Skip to content

Use WordPress custom post types and datatables to create a searchable staff table

Get the plugin from the repository

In this article I’m going to share the method I chose to create a searchable and filterable HTML table that contained the details of staff members on the client side, and was easy for admins to administer using WordPress.

The first thing we will do is create a mini filesystem in our WordPress theme folder to handle this functionality to keep things tidy and easy to maintain. This functionality is going to include:

  • Registering a Custom Post Type
  • Adding custom meta options to the Custom Post Type
  • Modifying the column output for the Custom Post Type where admins browse
  • Creating a Shortcode to display the datatable

So here is my recommendation for your mini filesystem to reside in your theme folder:


So to make sure we include all these files, first open ./staff/staff.php


And in your themes functions.php


Now we’re all linked up to our files correctly, let’s get on with the tasks. I’m going to concentrate first on registering and creating the administration parts, then go on to creating a functional, searchable table.

Registering the Staff Post Type

We need to make sure that admins can create, edit and remove staff members from the table. In order to do this we are going to register a new custom post type called ‘Staff’ which will appear in the admin menu in WordPress. WordPress gives us the ability to create our own labels for our new post type to make things easier manage.

In ./staff/post_type_register.php

register_post_type( 'staff_member',
        'labels' => array(
            'name' => __( 'Staff Members' ),
            'singular_name' => __( 'Staff Member' ),
            'menu_name' => __( 'Staff Members' ),
            'all_items' => __( 'All Staff Members' ),
            'add_new' => __( 'Add New Staff Members' ),
            'add_new_item' => __( 'Add New Staff Members' ),
            'edit_item' => __( 'Edit Staff Member' ),
            'new_item' => __( 'New Staff Member' ),
            'view_item' => __( 'View Staff Member' ),
            'search_items' => __( 'Search Staff Members' ),
            'not_found' => __( 'Staff Member Not Found' ),
            'not_found_in_trash' => __( 'Staff Member Not Found In Trash' ),
            'parent_item_colon' => __( 'Parent Staff Member' ),
        'public' => true,
        'has_archive' => true,
        'supports' => array('thumbnail'),
add_action( 'init', 'staff_member' );

Please note that register_post_type accepts an argument named ‘supports‘. This allows us to add an array of the WordPress default post support attributes such as title, editor and thumbnail. In this case we are going to use the default WordPress thumbnail support because it will be useful to add head shots of our staff. Apart from that we are going to be creating our own custom attributes that WordPress doesn’t have by default.

If this is working correctly, you will notice in the WordPress admin panel a new ‘Staff’ tab has been created. When you try to add a new ‘Staff Member’ you will notice the only thing available to edit will be the thumbnail.

Adding custom meta to Staff Members

Let’s add more than just the thumbnail. I am going to focus on some basic staff information to keep things simple but this is scalable to whatever you want.

  • First Name
  • Last Name
  • Position
  • Notes

So we want to create these input fields when we administer our Staff Member and we need to make sure the information we change is saved when we click publish. The first thing to do is create the form.

In ./staff/post_type_meta.php

// Function that adds a meta box to 'Staff Member'
function staff_meta() {
    add_meta_box( 'staff_meta', 'Staff Details', 'staff_meta_functions', 'staff_member', 'normal', 'high' );

Here, we have created a function that creates a new meta box to house our form using the WordPress add_meta_box method. Basically, the arguments from left to right are as follows.

  1. ID (staff_meta)
  2. Title (Staff Details)
  3. Callback (this is the function we are going to create that contains our form HTML)
  4. Post Type (this is obviously staff_member as referenced by our post type)
  5. Context (this defines where in the post the meta box appears. We’ve chosen normal, where the editor would normally by)
  6. Priority (the priority of where the meta box should show. We’ve chosen high so it stays at the top)

Now that this is understood, let’s add our callback function.

In ./staff/post_type_meta.php

... // End staff_meta()
function staff_meta_functions() {
    global $post;
    wp_nonce_field( plugin_basename( __FILE__ ), 'staff_meta_functions_nonce' ); // Create nonce to protect post
    // If meta exists, get it 
    $first_name = get_post_meta($post->ID, '_first_name', true);
    $last_name = get_post_meta($post->ID, '_last_name', true);
    $position = get_post_meta($post->ID, '_position', true);
    $notes = get_post_meta($post->ID, '_notes', true);
        <label for="first_name">First Name</label>
        <input class="widefat" type="text" name="_first_name" id="first_name" value="<?php
            if(isset($first_name)) {
                echo $first_name;
        ?>" />
        <label for="last_name">Last Name</label>
        <input class="widefat" type="text" name="_last_name" id="last_name" value="<?php
            if(isset($last_name)) {
                echo $last_name;
        ?>" />
        <label for="position">Position</label>
        <input class="widefat" type="text" name="_position" id="position" value="<?php
        if(isset($position)) {
            echo $position;
        ?>" />
        <label for="notes">Notes</label>
        <textarea class="widefat" rows="10" name="_notes" id="notes"><?php
            if(isset($notes)) {
                echo $notes;

So all this contains is a web form that contains values if they exist which they don’t at the moment because we are unable to save them. That’s the next bit.

In ./staff/post_type_meta.php

... // End staff_meta_functions()
function staff_meta_save($post_id, $post) {
    global $post;
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
        return false;
    if ( !current_user_can( 'edit_post', $post->ID ) ) {
        return $post->ID;
    if (!wp_verify_nonce( $_POST['staff_meta_functions_nonce'], plugin_basename( __FILE__ ) ) ) {
    } else {
        if($_POST['_first_name']) {
            update_post_meta($post->ID, '_first_name', $_POST['_first_name']);
        } else {
            update_post_meta($post->ID, '_first_name', '');
        if($_POST['_last_name']) {
            update_post_meta($post->ID, '_last_name', $_POST['_last_name']);
        } else {
            update_post_meta($post->ID, '_last_name', '');
        if($_POST['_position']) {
            update_post_meta($post->ID, '_position', $_POST['_position']);
        } else {
            update_post_meta($post->ID, '_position', '');
        if($_POST['_notes']) {
            update_post_meta($post->ID, '_notes', $_POST['_notes']);
        } else {
            update_post_meta($post->ID, '_notes', '');
    return false;

This code is checking if there are values in these meta fields and updating them as necessary. If the field is left blank, the value is nothing.

In order to get this to work, we need to make sure these functions are called at the right action hooks. We want staff_meta() to be called on admin_menu initiation and we want staff_meta_save() to be called on save_post.

add_action( 'admin_menu', 'staff_meta' );
add_action( 'save_post', 'staff_meta_save', 1, 2);

If all is well, when you now edit a ‘Staff Member’ post it should contain this form and the ability to save whatever you add/edit when you publish.

The staff member list and modifying the columns

If you click on ‘Staff Members’ in the admin panel and you have created some ‘Staff Member’ items then you will notice a list like you would normally see when viewing ‘Posts’ or ‘Pages’. It’s not quite how we want it. Since this post type has custom attributes we want to add some of these to our columns to make the items easier to identify when browsing. Here is how we do that.

First we want to create our new layout by removing whatever WordPress has decided to output and adding our own.

in ./staff/post_type_columns.php

// Custom column Display
add_filter( 'manage_edit-staff_member_columns', 'edit_staff_member_columns' ) ;
function edit_staff_member_columns( $columns ) {
    $columns = array(
        'thumbnail' => ('Thumbnail'),
        'first_name' => __( 'First Name' ),
        'last_name' => __(' Last Name' ),
        'position' => __('Position'),
        'edit' => __('Edit')
    return $columns;

By default, WordPress uses the ‘title’ of the item to link the item to it’s edit page. Since we’ve decided not to include this since we don’t need it, it is necessary to add our own means of linking to the edit page. I have added ‘edit’ to the columns.

Now that we’ve created our columns, we need to add what we want to display in them.

... // End edit_staff_member_columns()
// What to display in each list column
add_action( 'manage_staff_member_posts_custom_column', 'manage_staff_member_columns', 10, 2 );
function manage_staff_member_columns( $column, $post_id ) {
    // Get meta if exists
    $thumbnail = get_the_post_thumbnail( $post_id, 'thumbnail' );
    $first_name = get_post_meta( $post_id, '_first_name', true );
    $last_name = get_post_meta( $post_id, '_last_name', true );
    $position = get_post_meta( $post_id, '_position', true );
    switch( $column ) {
        case 'thumbnail' :
            if ( empty( $thumbnail))
                echo __( 'Unknown' );
                echo $thumbnail;
        case 'first_name' :
            if ( empty( $first_name ) ) {
                echo __( 'Unknown' );
            } else {
                echo $first_name;
        case 'last_name' :
            if ( empty( $last_name ) ) {
                echo __( 'Unknown' );
            } else {
                echo $last_name;
        case 'position' :
            if ( empty( $position ) ) {
                echo __( 'Unknown' );
            } else {
                echo $position;
        case 'edit' :
                echo '<a href="'.site_url('/wp-admin/post.php?post='.$post_id.'&action=edit').'">EDIT</a>';

First, we’re trying to get the meta information for the item if it exists. We iterate through our new columns that are unique to how we referenced them earlier. If this value exists then publish it in the column, if not return the text ‘Unknown’. This can be easily modified to suit yourself. For example, if a staff member doesn’t have an image then you could display a fall back image. The same goes for the other columns.

In addition to this, it might be nice if we had the ability to choose which columns we wanted to be able to sort the items by. Here is how you do that.

... // End manage_staff_member_columns()
// Which columns are filterable
add_filter( 'manage_edit-staff_member_sortable_columns', 'staff_sortable_columns' );
function staff_sortable_columns( $columns ) {
    $columns['first_name'] = 'first_name';
    $columns['last_name'] = 'last_name';
    $columns['position'] = 'position';
    return $columns;

We are adding a filter into WordPress sortable_columns and telling it which of our own columns we want our list to be filtered by.

If all is well here, when you click on ‘Staff Members’ in the admin panel if should display a custom table that presents the information in the columns of your items as you’ve specified it to do so.

Displaying the table by creating a shortcode

Since the requirement is that this table needs to be searchable and filterable, we’re going to need something a bit more complex than a simple HTML table. I have chosen to use jQuery DataTables by SpryMedia. Not only does it add the ability to search and filter the table, but it enhances the format as well. It is well documented and I highly recommend it.

  1. Download jQuery DataTables or use the CDN
  2. Copy jquery.dataTables.min.js to ./staff/asset/js or use the CDN
  3. Copy jquery.dataTables.css to ./staff/asset/css or use the CDN
  4. Copy contents of ./media/images to ./staff/asset/images

Now that we have our DataTable assets, we need to tell WordPress to load them.

In ./staff/staff.php

... // End includes
function staff_assets(){
    wp_register_script( 'jquery_datatables_js',  get_template_directory_uri() . '/staff/asset/js/jquery.dataTables.min.js', array(),null,true );
    wp_enqueue_script( 'jquery_datatables_js' );
    wp_register_style( 'jquery_datatables_css',  get_template_directory_uri() . '/staff/asset/css/jquery.dataTables.css');
    wp_enqueue_style( 'jquery_datatables_css' );
add_action('wp_enqueue_scripts', 'staff_assets');

We are now loading our assets on our website. Now let’s see how we can use them.

We need to create a shortcode that we can embed in our page to display the table with our staff members in it. To do this we will use the WordPress loop but make sure we are only returning items that are of the Staff Members post type. Here is how we’re going to do this.

In ./staff/post_type_shortcode.php

function list_staff() {
    // Only staff members
    $args = array(
        'post_type' => 'staff_member',
        'posts_per_page' => 9999 // Set to high number to override default posts limit
    $query = new WP_Query( $args );
        if ($query->have_posts()) :
            // Specify this table is a dataTable and assign an ID
            echo '<table class="tablepress dataTable" id="staff_table">';
            echo '<thead>';
            echo '<tr>';
            echo '<th>Image</th>';
            echo '<th>First Name</th>';
            echo '<th>Last Name</th>';
            echo '<th>Position</th>';
            echo '<th>Notes</th>';
            echo '</tr>';
            echo '</thead>';
            while( $query->have_posts() ) : $query->the_post();
                // Get meta if exists
                $thumbnail = get_the_post_thumbnail( get_the_ID(), 'thumbnail' );
                $first_name = get_post_meta( get_the_ID(), '_first_name', true);
                $last_name = get_post_meta( get_the_ID(), '_last_name', true);
                $position = get_post_meta( get_the_ID(), '_position', true);
                $notes = get_post_meta( get_the_ID(), '_notes', true);
                        if($thumbnail) {
                            echo $thumbnail;
                        } else {
                            echo 'No Image';
                    <td><?php echo $first_name; ?></td>
                    <td><?php echo $last_name; ?></td>
                    <td><?php echo $position; ?></td>
                    <td><?php echo $notes; ?>
                        <?php if ( current_user_can('edit_post', get_the_ID()) ) {
                            // Edit button if current user can edit staff members
                            echo '<span class="edit-link"><a class="post-edit-link" href="/wp-admin/post.php?post='.get_the_ID().'&action=edit">EDIT</a></span>';
                        } ?>
            echo '</table>';
            <script type="text/javascript">
                    // Init dataTable
                        aoColumnDefs: [
                                bSortable: false,
                                aTargets: [ 0,4 ]

This function will output an HTML table that contains the details of each staff member and transforms it into a searchable and filterable dataTable. Without going too much in depth of the jQuery dataTable() function, here is what I did.

  • “asStripeClasses”:[‘even’,’odd’] changes the background colour of odd/even rows
  • “bSort”:true Enable sorting
  • aoColumnDefs: [{ bSortable: false, aTargets: [ 0,4 ]}] Specify which columns are NOT sortable. In this case, thumbnail(0) and notes(4)

More on jQuery DataTables column options here.

There is one other thing we need to do here before registering this function as a short code. Because the function is echoing the output and not returning a value, we need to put it’s output inside an object.

in ./staff/post_type_shortcode.php

... // End list_staff()
function list_staff_obj($atts, $content=null) {
    list_staff($atts, $content=null);
    return $output;

Now we’re returning the output, let’s associate that output to a shortcode.

... // End list_staff_obj
add_shortcode( 'list-staff', 'list_staff_obj' );

If all is well, you should be able to add the shortcode [list-staff] into your pages or posts and it should display the DataTable with all the Staff Members in it.

Get the plugin from the repository


10 years + experience in web development working with lots of different technology.

Published inWordPress


  1. Hassan Hassan

    Hi Joe,
    I would like to use datatable jquery with wordpress. I downloaded your code and tried to implement but some how the plugin does not work. Would you please let me know how to implement your plugin.

    Best Regards,

    • Let me know the problems you’re having I will try to assist.

  2. Ben Ben

    Was looking for a way to display (and maintain through the admin panel) an HTML table. I was sure that a CPT could get the job done and I guess I was right. Gonna read your post a little further later. Thanks!

  3. Is there any support for the table actions; eg csv, print, excel, copy etc?

  4. GG GG

    What would it take to filter the table you’ve created from an external form. For example, if I wanted to have an input on a external page that said “search by zip code” and then it went to the form page and filtered the zip codes to find a match.

    Great tut by the way.

Leave a Reply

Your email address will not be published. Required fields are marked *