What are Custom List Table Columns? What are Custom List Table Columns?

A List Table is the output of the generic WP_List_Table class object on various admin screens that list various WordPress data types in table form such as posts, pages, media, etc. Naturally, tables are divided into columns that display a particular type of information. Each table has a set of predefined default columns that are displayed. For example, the Posts Table has Title, Author, and Categories columns, amongst others.

screen grab showing added custom column

Custom column added to Custom Post Type List Table showing metabox values

 

Custom columns are columns that plugins can add to any of these tables to display plugin specific information related to the data type of the table.

Top ↑

Why use Custom List Table Columns? Why use Custom List Table Columns?

There are probably very few, if any instances where a plugin has to add custom columns to a table. There is always some other way to convey information, a separate settings page or a completely separate custom table.

On the other hand, there are several reasons to add custom columns. Users are already familiar with the List Tables. They know these tables contain information and action links related to each data type for which the table exists. It thus makes complete sense for plugins that have additional information or action links related to the same data type to include such information on the same table.

Doing so enhances the user experience by placing related information and actions where they would intuitively expect to find it. Adding a custom column can be cleaner and more efficient than adding an entire new settings page to represent the same thing.

In the case of needing to add several custom columns, so many that they would not fit on the default table, creating a new table based on the same WP_List_Table that all other data types use presents a familiar look and feel that WordPress users are already familiar with. Users are then made more comfortable with the strange new components of your plugin. Your plugin will be better integrated with the default look and feel of other admin screens by using custom columns.

Top ↑

How WP_List_Tables Work How WP_List_Tables Work

The first paragraph of this article mentioned that the List Tables were output by a generic WP_List_Table class object. This is not exactly correct. The actual tables are output by objects created from extensions of that generic class. Each WordPress data type that is displayed in a table has its own extension. For example, posts tables are output by an object of the WP_Posts_Lists_Table class. All these classes are defined in the wp-admin/includes folder. If you check the source code for any of these data type classes, you will see they all extend WP_List_Table class.

Typical of a base class, all the generic properties and methods are defined here. The extensions then define properties and methods that specifically address any peculiarities of the particular data type. Doing it this way ensures a consistent user interface between all the different data types. When a List Table object is instantiated, some basic data relating to the admin screen is initialized. Next, the prepare_items() method is called to establish various counts of certain data elements. Finally, the display() method is called to output the table.

display() first outputs the various elements that appear above the table, such as views, bulk actions, and pagination. Then the table column labels are output by first calling get_column_headers(). This is your opportunity to add custom columns via the 'manage_{$screen->id}_columns' filter. The {$screen->id} portion of the filter tag is a variable value depending on the context when the filter is initiated. See the Hook Summary for values used for all the various contexts.

A query is made to get all the table items. The query is restricted by the user selections from the view, filter, page, etc. form elements. Each item returned is stepped through in turn, outputting a single row per item by calling single_row(). This method outputs a single row by stepping through all the column headers. Each column in the default table is represented in a switch/case structure. When a particular header comes up in the loop, it is matched to a case and the associated code is executed to output the actual table cell content. In the case of custom columns that were added, no case match is found, so the default case code is called, which typically consists of do_action('manage_{$screen->id}_custom_column', $name, $id);, though the exact code varies by administration screen and its extended class. Hooking into this action enables you to output appropriate content in the correct part of the table.

Top ↑

How do I Add Custom List Table Columns? How do I Add Custom List Table Columns?

First determine the screen->id for the admin screen you are interested in. You will need this to ensure your columns end up on the right table. You will use the screen->id as an identifier for two different filter/action tags that are used to insert custom columns. One filter is to simply add the names of your custom columns, the other is used to generate the content of each table cell in your columns.

Besides adding columns, there are other ways to alter what the list tables do. We’ll take a look at those techniques as well after we cover the technique for adding custom columns.

Define Your Custom Columns Define Your Custom Columns

To add a custom column, you must first add its name to the array of column header names. This is done by hooking into the 'manage_{$screen->id}_columns' filter. Your callback is passed the current column header name array. Simply add another key/value pair to the array and return the entire array. The key can be any arbitrary unique string and the value is the actual column name that will appear in the table header. The following example defines a ‘Metabox’ column to be added to a ‘portfolio’ custom post type table.

function my_cpt_columns( $columns ) {
    $columns["metabox"] = "Metabox";
    return $columns;
}
add_filter('manage_edit-portfolio_columns', 'my_cpt_columns');
//that's all that's needed!

Top ↑

Sortable Columns Sortable Columns

You can only sort columns that contain data from the database related to the table items because the sort is achieved by re-querying the table contents. If your data cannot be related to the table objects in a mySQL query, then it cannot be used for sorting of your column. If you don’t need to sort columns, the data can come from anywhere the script can access, pretty much the entire Internet.

After defining your column you make it sortable by first hooking the sortable filter variant 'manage_{$screen->id}_sortable_columns' and adding your column name to that array as well. This can often be the same callback as the one for adding the initial non-sortable column.

function my_cpt_columns( $columns ) {
    $columns["metabox"] = "Metabox";
    return $columns;
}
add_filter('manage_edit-portfolio_columns', 'my_cpt_columns');
add_filter('manage_edit-portfolio_sortable_columns', 'my_cpt_columns');
//the above line is the only difference from the previous example

You then hook the 'request' filter and check the passed query variables array for your column name under the 'orderby' key. If found, set query arguments that can be used to query the table contents in the correct order (typically by setting the 'orderby' variable) and merge into the passed variables. Clicking the column head will toggle the query order between ASC and DESC

The query arguments you set are the same arguments you define in a custom new WP_Query object. You essentially define an arguments array as if for a new query object and merge it with the array passed by the 'request' filter. The 'request' filter is initiated from the parse_request() method of the WP class. It is defined in wp-includes/class-wp.php. Your callback is passed $this->query_vars. The following example sorts the Metabox column by the values stored under the '_my_meta_value_key' in the postmeta table.

function my_sort_metabox( $vars ) {
      if( array_key_exists('orderby', $vars )) {
           if('Metabox' == $vars['orderby']) {
                $vars['orderby'] = 'meta_value';
                $vars['meta_key'] = '_my_meta_value_key';
           }
      }
      return $vars;
}
add_filter('request', 'my_sort_metabox');
//the ASC and DESC part of ORDER BY is handled automatically

Top ↑

Horizontally Reordering Columns Horizontally Reordering Columns

The table column order is predefined of course. When you add your custom column, it will appear at the end of the list at the far right edge of the table. If you want your column to appear elsewhere, or simply wish to rearrange the order of the default columns, here is what to do. While you are hooked into the 'manage_{$screen->id}_columns' filter, instead of working with the array passed, copy the elements in the desired order into a new array. Return this new array and it’s order will be used instead of the default. The following example moves our Metabox column to appear before the Date column. As it only involves the last column, instead of redefining the entire array, we leave the first part alone and only unset, then reset the date column we want to move to the end. You can var_dump($columns) to see the key/value used for each column.

function my_cpt_columns( $columns ) {
    $date = $columns['date'];
    unset $columns['date'];
    $columns['metabox'] = 'Metabox';
    $columns['date'] = $date;
    return $columns;
 }
 add_filter('manage_edit-portfolio_columns', 'my_cpt_columns');
//move date column to right of our custom column

Top ↑

Output Table Cell Contents Output Table Cell Contents

To output your custom column data into the table, hook the 'manage_{$screen->id}_custom_column' action or whatever specific hook applies to the admin screen you are customizing. Your callback must check the passed data to identify which column and row data should be output. Retrieve the data as required and echo it directly to the client browser. This example outputs the stored postmeta values associated with each custom post type.

function my_cpt_column( $colname, $cptid ) {
     if ( $colname == 'metabox')
          echo get_post_meta( $cptid, '_my_meta_value_key', true );
}
add_action('manage_portfolio_posts_custom_column', 'my_cpt_column', 10, 2);
//the actual column data is output

TOP TIP
If your custom column is not showing itself on the expected table, your code may be fine. Check your admin screen options before debugging your code. You may need to simply check the checkbox corresponding to your custom column.

Interactivity Interactivity

You can make your column data interactive by presenting the column data as specific action links. Or better yet by using jQuery to capture mouse events such as onclick or mouseover. Such events can initiate an AJAX request, and depending on the response from the server, alter what is being displayed to the user. For example, if the custom column represented a Boolean state, clicking on the data can toggle it between true and false. Concurrently, the corresponding data in the database is also updated to the new state via AJAX requests.

That example is very basic of course. The potential user experience (UX) you can create using AJAX techniques is virtually endless. The details on implementing such things with AJAX are detailed in Improve UX with AJAX of this Handbook.

Top ↑

These are the links that appear under the titles of each item in a table, usually on hover. Each link performs a particular action on that item. For example, the posts table has action links for Edit, Quick Edit, Trash, View.

Top ↑

Other Elements Other Elements

Hooks exist to alter other elements on the tables, but in reality what you can do with these hooks is quite limited. The information is included mainly for the sake of completeness rather than any desirable utility.

Top ↑

Views Views

These are the links at the top of the table that afford different views of the data. For example, the Posts table offers views for All, Published, Trash. To add a view, study the source code that outputs the current page and how it handles parameters that are passed for the existing view options. A new view needs to work within this system, unless you are ready to develop an entirely new view handling script. Hook into the appropriate 'views_{$screen->id}' filter and add a new element to the passed array. The key is an arbitrary tag for your view and the value is the HTML to be output at the appropriate time. The pipe ‘|’ separator is added automatically, it does not need to be part of your HTML. The HTML can be anything actually, but the idea is to cause the table to reload with varying parameters, such as different post statuses for the posts table. Of course, the destination page needs to be able to understand the URL parameters being sent to it. What the page can understand and to what extent you can alter it’s understanding will vary by the table type.

The following snippet adds a “Watch” link to the comments table. The link will not work because the target page does not know what to do with a “watch” parameter. The point here is to illustrate the code required to add a link, not how to work with the edit-comments.php page.

add_filter('views_edit-comments', 'my_table_view');
function my_table_view( $views ) {
   $view = '<a href=' . admin_url('edit-comments.php?comment_status=watch') . '>Watch</a>';
   $views['watch'] = $view;
   return $views;
}

Top ↑

Filter Dropdowns Filter Dropdowns

The filter dropdowns at the top of the table are called “extra tablenav” in source code. The availability of hooks to alter these is limited to a few screens. It is not possible to alter what is already offered. If any hook is provided at all, it is an action in which you can output additional HTML or perform some other operation after the dropdown is output but before the “Filter” button is output. Note the hooks are actions for the Filter dropdown, not filters! When the filter button is clicked, a GET request is sent with the form element values as URL parameters. The request is sent to the same page that is responsible for displaying the table. This page thus needs to know how to deal with anything you add to the form. The form fields are used to construct a new query of table items. One real possibility is you could output hidden fields that become WP_Query vars that further restrict the query.

Alternately, enqueue some jQuery to handle an added field’s onchange event, bypassing the Filter button and the table’s page. The extra tablenav actions are listed in the Hooks Summary. The following snippet adds “Click to” after the filter dropdown on the comments table. This obviously serves no purpose. More substantive content is left to others.

add_action('restrict_manage_comments', 'my_restrict');
function my_restrict( $options ) {
   echo 'Click to';
}

Top ↑

The base list table and the default extensions have no filters to alter the search box. Plugin extended classes may have additional filters or actions. Check the source code of the applicable class for a search_box() method that may offer a hook of some sort.

Top ↑

Bulk Actions Bulk Actions

There is no convenient way to add bulk actions to the existing options, only removing options is possible. Short of hacking core code which is a serious no-no, about all you could do to add an action is to extend the base class with your own version of the table, remove the existing admin screen and replace it with your own. This is almost as bad as hacking core code because you lose the benefit of that screen being maintained by the core developers. The onus then falls on you to keep up with any changes to the original screen and migrate such changes to your own code as needed.

It much easier to remove a bulk action option. Hook the appropriate 'bulk_actions-*' filter and unset the desired item in the table passed. Return the revised table. The following code removes the “Approve” bulk action so users must individually approve every comment.

add_filter('bulk_actions-edit-comments', 'my_remove');
function my_remove( $actions ) {
   unset( $actions['approve']);
   return $actions;
}

Top ↑

View Switcher View Switcher

These are the buttons that allow one to select either List or Excerpt views. The base list table has no filters to alter the switch options. Plugin extended classes may have additional filters or actions, but the default screens for WordPress do not. Check the source code of the applicable class for a view_switcher() method that may offer a hook of some sort.

Top ↑

Hook Summary Hook Summary

The following is a comprehensive summary of action and filter hooks related to all the default WordPress List Tables and administration screens. It is accurate for version 3.9, the latest version at the time of this writing. The information is subject to change with subsequent versions. It’s best to confirm the hook still works as intended by examining the related source code for the version you are using.

Top ↑

Column Definition, Views, & Bulk Actions Column Definition, Views, & Bulk Actions

These filters and actions all use $screen->id to comprise a portion of their tag. The following discussion reveals from what function the filter or action is initiated and in what file it is declared. You also learn what parameters are passed to your callback function.

  • Column Name Filter – applied by the get_column_headers() function. The filter tag has the form "manage_{$screen->id}_columns" and can be found in wp-admin/includes/screen.php. Your filter callback is passed the column definition array. The values for $screen->id in the various admin screens are listed below. It’s easy to confuse this filter with similar actions or filters used to output the table content. Note that this filter for column names always has the plural form of ‘columns’ in its name. The ‘column’ in action and filter names for outputting table content is always singular. Also note both underscores and hyphens could appear in screen IDs, they are not interchangeable. Don’t get them mixed up!
  • Sortable Column Name Filter – this variant is applied by the get_column_info() method of the WP_List_Table class. It has the form "manage_{$screen->id}_sortable_columns" and can be found in wp-admin/includes/class-wp-list-table.php. Your filter callback is passed the sortable column array. The values for $screen->id in the various admin screens are listed below.
  • Views Filter – called by the views() method of the applicable table class. The tag has the form of "views_{$screen->id}". The callback is passed the $views array.
  • Bulk Actions Filter – called by the bulk_actions() method of the applicable table class. The tag has the form of "bulk_actions-{$screen->id}". Note the hyphen before the screen id. The callback is passed the $actions array.
Screen
$screen->id
Comments
'edit-comments'
MS Sites
'sites-network'
MS Themes
'themes-network' – individual blogs use 'themes'
MS Users
'users-network' – individual blogs use 'users'
Plugins
'plugins'*
Posts
'edit-post'
Pages
'edit-page'
Custom Post types
“edit-{$post->post_type}”
Categories
'edit-category'
Tags
'edit-post_tag'
Terms
“edit-{$this->screen->taxonomy}”
Themes
'themes'*
Media
'upload'
Users
'users'*

*Append -network to MS Admin screens, individual blog screens use the same IDs as regular sites.

Top ↑

Table Cell Content Table Cell Content

You hook these actions and filters to output the actual cell contents of a custom column for the tables on various administrative screens. Each List Table type link below leads to a data table for that List Table type. Note that while most of these hooks are actions, a few are filters. The Filter designation in the A/F row in each table is always bold to remind you to use add_filter() instead of the more common add_action(). The hook tags in all cases here end with ‘column’, the singular form. The hook tags for defining custom columns always end with ‘columns’, the plural form. Some of the parameters passed to your callback are listed in the linked tables as ''. This indicates two single quotes representing an empty string. It appears to be one double quote, but that would not make sense as there is no corresponding close quote. For filters, you must always return the first parameter passed. The remaining parameters are available for your use if you need them.

The File row in each table lists the source code file in which the table class is defined. In all cases the files are found in /wp-admin/includes/. Click the List Table types below to view a table of related data.

Top ↑

Table Type Table Type

Top ↑

These filters are used to add action links that appear under the title of each item in a table, usually on hover. The filters cannot be added when your plugin or theme loads. To add your filter, the add_filter() call must be done from within an action callback for the 'admin_menu' action.

The following example adds a link under each comment that reports the comment to a third party spammer identification service. Important! This code is obsolete because the commenter’s email is not verified, violating the SFS TOS. Do not use this script, it is an example for adding row action links only, not for actually reporting spammers.

add_action('admin_menu', 'my_init');
function my_init() {
   add_filter('comment_row_actions','my_report', 10, 2 );
}
function my_report( $actions, $comment ) {
   $uname = urlencode( $comment->comment_author );
   // email MUST be verified by commenter before submitting!
   $email = urlencode( $comment->comment_author_email );
   $ip = $comment->comment_author_IP;
   $evidence = "Profile Link: " . $comment->comment_author_url . "\nPost Content: " . $comment->comment_content;
   $evidence = urlencode( $evidence );
   $apikey = "Get your own API key and place here";
   $action = "<a title=\"Report to Stop Forum Spam (SFS)\"target=\"_stopspam\" href=\"http://www.stopforumspam.com/add.php?username=$uname&email=$email&ip_addr=$ip&evidence=$evidence&api_key=$apikey\">Report to SFS</a>";
   $actions['report_spam'] = $action;
   return $actions;
}
Screen
Fiter tag
Parameters
File
Comments
'comment_row_actions'
$actions, $comment
dashboard.php
MS Sites
'manage_sites_action_links'
$actions , $blog['blog_id'], $blogname
class-wp-ms-sites-list-table.php
MS Themes
'theme_action_links'
$actions, $theme, $context
class-wp-ms-themes-list-table.php
MS Users
'ms_user_row_actions'
$actions, $user
class-wp-ms-users-list-table.php
Plugins
*'plugin_action_links' or

*"plugin_action_links_{$plugin_file}"
$actions , $plugin_file,
$plugin_data, $context
class-wp-plugins-list-table.php
Posts
'post_row_actions'
$actions, $post
class-wp-posts-list-table.php
Pages
'page_row_actions'
$actions, $post class-wp-posts-list-table.php
Custom Post Types
'post_row_actions' (non-hierarchical)

'page_row_actions' (hierarchical)
$actions, $post
class-wp-posts-list-table.php
Categories
'category_row_actions'
$actions, $tag
class-wp-terms-list-table.php
Tags
'tag_row_actions'
$actions, $tag
class-wp-terms-list-table.php
Terms
"{$taxonomy}_row_actions"
$actions, $tag
class-wp-terms-list-table.php
Themes
'theme_action_links' or

"theme_action_links_{$stylesheet}"
$actions, $theme
class-wp-themes-list-table.php
Media
'media_row_actions'
$actions, $post, $this->detached
class-wp-media-list-table.php
Users
'user_row_actions'
$actions, $user_object
class-wp-users-list-table.php

*Prepend network_admin_ to tags for MS Admin screens, individual blogs use the same tags as regular sites.

Top ↑

Filter Dropdowns (Extra Tablenav) Filter Dropdowns (Extra Tablenav)

These actions pass no parameters. The actions fire after the dropdown is output but before the ‘Filter’ button is output.

Screen
Action Tag
File
Posts
'restrict_manage_posts'
class-wp-posts-list-table.php
Comments
'restrict_manage_comments'
class-wp-comments-list-table.php
Media
'restrict_manage_posts'
class-wp-media-list-table.php