Modifying Administration Tables


Admin Table
July 2013

Contents



Modifying Administration Tables


The WordPress administration panels make extensive use of the WP_List_Table class to organize and display data related to a wide variety of WordPress features. You may or may not know it's possible to add custom columns to any of these tables. And even if you did know, there's a good chance you don't know how it's done because the CodexLink is to outside of this site is very sparse on this sort of information. If you would like to know how custom columns are added to admin tables or how to alter other table features, you came to the right place, we're going to dive into this topic right now.

Prerequisites

This article assumes you have reasonable PHP coding skills in hacking WordPress. You must have a reasonable understanding of PHP, including array manipulation, Object Oriented Programming and extended classes. You must understand how to hook intoLink is to outside of this site WordPress filters and actions, including those that pass arrays, objects, or multiple parameters. You need to be familiar with filter and action tags that use variable elements. You also need a basic understanding of HTML coding.

How WP_List_Tables Works


The WP_List_Table class is a basic table framework for displaying data in table form. Administration panels for each data element, such as Posts or Links, extend this class, redefining some properties and methods and creating others anew. These class definitions are all in the wp-admin/includes/ folder. The class offers several standard elements to make interacting with the table easy, and also has the effect of standardizing the user experience from panel to panel.

The main thing most developers are concerned with is adding custom columns to the main table. When a new table class is instantiated, some basic data relating to the admin panel are initialized. Next, prepare_items() is called to establish various counts of certain data elements. Finally, display() is called to output the table.

The display() method 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 before get_column_headers() returns 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 most contexts.

A query is made to get all the table elements using the view, filter, page, etc. selections to restrict the query. Each element returned is stepped through in turn, outputting a single row per element by calling single_row(). This function outputs a 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. Hooking into this action enables you to output appropriate content at the correct time.
to Contents

Define Your Custom Column


Column 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 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 bcw_cpt_columns($columns) {
	$columns["metabox"] = "Metabox";
	return $columns;
}
add_filter('manage_edit-portfolio_columns', 'bcw_cpt_columns');

Sortable Columns

You can only sort columns that contain data from the database related to the table objects, as 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.

After defining your column you can make it sortable by 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 bcw_cpt_columns($columns) {
	$columns["metabox"] = "Metabox";
	return $columns;
}
add_filter('manage_edit-portfolio_columns', 'bcw_cpt_columns');
add_filter('manage_edit-portfolio_sortable_columns', 'bcw_cpt_columns');
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 the orderby column) 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.
function bcw_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', 'bcw_sort_metabox');

Horizontally Reordering Columns

By default, the column order is predefined. So when you add your custom column, it appears 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 default columns, 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 names used for each column.
function bcw_cpt_columns($columns) {
	$date = $columns['date'];
	unset $columns['date'];
	$columns['metabox'] = 'Metabox';
	$columns['date'] = $date;
	return $columns;
}
add_filter('manage_edit-portfolio_columns', 'bcw_cpt_columns');
to Contents

Output Table Cell Contents

Jail cellTo 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 remote client browser.
function bcw_cpt_column($colname, $cptid) {
	if ($colname == 'metabox')
		echo get_post_meta( $cptid, '_my_meta_value_key', true );
}
add_action('manage_portfolio_posts_custom_column', 'bcw_cpt_column', 10, 2);

Interactivity

You can make your column data interactive 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 DB is also updated to the new state via AJAX requests.

The details on implementing this are beyond the scope of this article, but check out my article on WordPress AJAX techniques.

Row Action Links


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

You can add your own action links to a table by hooking into the appropriate {$screen->id}_row_actions filter and adding your own element to the array passed, then return the whole array. The added element's key is an arbitrary label for your action link and the element's value is the HTML string to be output at the appropriate spot in the table. The filter to use for various tables is listed in the Hook Summary.

The row action 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 code snippet adds another action link on hover to the comments table under each comment listed. The action link output by this code would actually 404 if you clicked on it because there is no file named act.php in a typical WordPress installation. The link HTML is just an example, not a functional link. Note how the comment ID is included as an URL parameter so the fictious php file would know which comment to act upon.
add_action('admin_menu', 'bcw_init');
function bcw_init() {
	add_filter('comment_row_actions','bcw_action', 10, 2);
}
function bcw_action($actions, $comment) {
	$action = "<a href=\"http://example.com/act.php?comment={$comment->comment_ID}\">Act Now</a>";
	$actions['act_now'] = $action;
	return $actions;
}
to Contents

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.

NEW! - Beginning with WordPress 4.7 (targeted release December 2016), developers will be able to register custom bulk actions. Unlike other hooks mentioned below, this feature IS useful.

Views

These are the links at the top of the table that affords 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', 'bcw_view');
function bcw_view( $views ) {
	$view = "<a href=\"http://example.com/wp-admin/edit-comments.php?comment_status=watch\">Watch</a>";
	$views['watch'] = $view;
	return $views;
}

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 panels. 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, the form is sent as a GET request to the same page as the one displaying the table in the first place, so that page needs to know how to handle the any form fields that you added. The form fields are used to construct a new query, so you could output hidden form fields that contain additional 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. I leave more substantive content to others.
add_action('restrict_manage_comments', 'bcw_restrict');
function bcw_restrict($options) {
	echo 'Click to';
}
to Contents

Search Box

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.

Bulk Actions

Bulky box Prior to version 4.7, 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 is extend the base class with your own version of the table, remove the existing admin panel and replace it with your own. This is almost as bad as hacking core code because you lose the benefit of that panel being maintained by the core developers. The onus falls on you to keep up with any changes to the original panel and migrate such changes to your own code as needed.

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', 'bcw_remove');
function bcw_remove($actions) {
	unset($actions['approve']);
	return $actions;
}

Adding Bulk Actions

With the release of WordPress 4.7, you'll be able to register bulk actions that can be applied to list table content. There are three filter hooks to use. First use the "bulk_actions-{$screen->id}" filter to register your action. This is simlar to how columns are added. You add your bulk action to the passed array of current bulk actions. Then use the "handle_bulk_actions-{$screen->id}" filter to handle the actual actions. First your callback must verify the hook fired for your particular action (passed as $doaction) because all bulk actions fire this same hook. You can then step through the passed object IDs to execute the intended action on each object.

You should also add an URL parameter to the passed redirect URL so that an appropriate message can be displayed to the user after the actions complete. The action 'admin_notices' is then used to output an appropriate message based on the URL parameter added to the redirect URL. More information and examples can be found at the WordPress core developer's "Make" siteLink is to outside of this site.

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 panels 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.
to Contents

Hook Summary


Grappling Hook 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.5.2, 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.

Column Definition, Views, & Bulk Actions

The column name filter is 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 panels 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.

The sortable column name filter 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 panels are listed below.

The views filter is 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.

The bulk actions filter is 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.

The filter that handles the bulk actions is "handle_bulk_actions-{$screen->id}", available beginning with WordPress 4.7. Three parameters are passed, $redirect_to, $doaction, $post_ids. This filter is applied from the corresponding edit object file, such as edit.php, edit-comment.php, etc.

Panel
$screen->id
Links
'link-manager'
Link Categories
'edit-link_category'
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.
to Contents

Table Cell Content

These actions or filters are hooked to output the actual cell contents of a custom column for the tables on various administrative panels. Note that while most of these hooks are actions, a few are filters. The Filter designation in the A/F column 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 below 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 column lists the source code file in which the table class is defined. In all cases the files are found in /wp-admin/includes/ . This table is very wide. Use the scroll bar at the bottom to view all the content of each row.

Panel
A/F
Tag
Parms (''=empty string)
File
Links
Action
'manage_link_custom_column' $column_name, $link->link_id class-wp-links-list-table.php
Link Categories
Filter
'manage_link_category_custom_column'
'', $column_name
class-wp-terms-list-table.php
Comments
Action 'manage_comments_custom_column'
$column_name, $comment->comment_ID
class-wp-comments-list-table.php
MS Sites
Action 'manage_sites_custom_column' $column_name, $blog['blog_id']
class-wp-ms-sites-list-table.php
MS Themes
Action 'manage_themes_custom_column'
$column_name, $stylesheet, $theme
class-wp-ms-themes-list-table.php
MS Users
Filter 'manage_users_custom_column'
'', $column_name, $user->ID
class-wp-ms-users-list-table.php
Plugins
Action 'manage_plugins_custom_column'
$column_name, $plugin_file, $plugin_data
class-wp-plugins-list-table.php
Posts
Action 'manage_posts_custom_column'
$column_name, $post->ID
class-wp-posts-list-table.php
-Alt for hierarchical posts
Action 'manage_pages_custom_column'
$column_name, $post->ID
class-wp-posts-list-table.php
-Alt for CPTs
Action "manage_{$post->post_type}_posts_custom_column"
$column_name, $post->ID class-wp-posts-list-table.php
Pages
Action 'manage_pages_custom_column'
$column_name, $post->ID class-wp-posts-list-table.php
-Or
Action 'manage_page_posts_custom_column'
$column_name, $post->ID class-wp-posts-list-table.php
Custom Post Types
Action "manage_{$post->post_type}_posts_custom_column"
$column_name, $post->ID class-wp-posts-list-table.php
Categories
Filter 'manage_category_custom_column'
'', $column_name, $tag->term_id
class-wp-terms-list-table.php
Tags
Filter 'manage_post_tag_custom_column'
'', $column_name, $tag->term_id
class-wp-terms-list-table.php
Terms
Filter "manage_{$this->screen->taxonomy}_custom_column"
'', $column_name, $tag->term_id
class-wp-terms-list-table.php
Media
Action 'manage_media_custom_column'
$column_name, $id
class-wp-media-list-table.php
Users
Filter 'manage_users_custom_column'
'', $column_name, $user_object->ID
class-wp-users-list-table.php

to Contents

Row Action Links

Rowing Icon These filters are used to add action links that appear under the title of each item on hover in a table. 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.
function bcw_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;
}
function bcw_init() {
	add_filter('comment_row_actions','bcw_report', 10, 2);
}
add_action('admin_menu', 'bcw_init');

Panel
Fiter tag
Parameters
File
Links
n/a


Link Categories
'link_category_row_actions'
$actions, $tag
class-wp-terms-list-table.php
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.


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

to Contents

«Back


Comments, feedback, and questions are always welcome. Email me at JavaScript needs to be enabled .