Adding Columns to Posts in Admin (Sortable)
Let's talk about creating new columns in the posts table in the admin area. We will also look at how to make such columns sortable, just like the date column. This way, we can create a column with data from a meta-field and then sort the posts based on that data. It's all quite simple.
In this article, as an example, we will create a column "Visits" with data from the meta-field views, where visits are recorded. The column will be sortable.
Hooks for Creating Columns
- manage_(screen_id)_columns
Allows adding columns to the posts table on the specified screen (in our case edit-post).
Passes an array with column data that we can modify by adding our column (views) or removing an existing one using unset(). The name of our filter will be:
manage_edit-post_columns
.- manage_(post_type)_posts_columns
- Similar to the previous one - adds columns. Here, the post type is specified, not the screen ID... The name of our filter will be:
manage_post_posts_columns
. Since version 3.1, it is recommended to use this hook. - manage_(post_type)_posts_custom_column
Responsible for filling the column data on the posts page. In our case:
manage_post_posts_custom_column
.Passes the column name and post ID.
- manage_(screen_id)_sortable_columns
Similar to the first one — registers a sortable column where we specify the orderby query name.
Also passes an array with registered sortable columns. In our case, the filter looks like this:
manage_edit-post_sortable_columns
.- pre_get_posts (wp-includes/query.php)
This filter-action triggers at the very beginning of the get_posts() method of the WP_query class.
The hook passes the entire class by reference (&$this). Using this filter, we can set the parameters of the main WP query ($wp_query), based on which the output is then built.
To find out the screen_id, we use the function get_current_screen(). It can be hooked to in_admin_header:
add_action( 'in_admin_header', function(){ echo '<pre>'. print_r( get_current_screen(), 1 ) .'</pre>'; } );
In our case, screen_id = edit-post
- this is the post editing page in the admin area.
Creating the Column
Insert the following code into the theme file function.php:
// create a new column add_filter( 'manage_' . 'post' . '_posts_columns', 'add_views_column', 4 ); function add_views_column( $columns ) { $num = 2; // after which column to insert new ones $new_columns = [ 'views' => 'Visits', ]; return array_slice( $columns, 0, $num ) + $new_columns + array_slice( $columns, $num ); } // fill the column with data // wp-admin/includes/class-wp-posts-list-table.php add_action( 'manage_' . 'post' . '_posts_custom_column', 'fill_views_column', 5, 2 ); function fill_views_column( $colname, $post_id ) { if( $colname === 'views' ){ echo get_post_meta( $post_id, 'views', 1 ); } }
At this point, we can stop if we do not need to sort the column — it will simply display the data.
Making the Column Sortable
// add the ability to sort the column add_filter( 'manage_edit-' . 'post' . '_sortable_columns', 'add_views_sortable_column' ); function add_views_sortable_column( $sortable_columns ) { $sortable_columns['views'] = [ 'views_views', false ]; // false = asc (default) // true = desc return $sortable_columns; }
Here the key views
must match the key when registering the column: $out['views']
and $sortable_columns['views']
. The value: views_views will be the value of the orderby query parameter that WordPress will automatically add (&orderby=views_views). This same value will be added to the WP_query request parameters and if it matches known WP values ('title', 'date', 'modified', 'comment_count', etc.), WP will sort the column as needed and we can stop here. Full list of known WP values, exceptions are: meta_value and meta_value_num.
If we specify meta_value instead of views_views, WP will not be able to automatically perform the correct sorting. Therefore, if the orderby parameter specifies our value views_views (&orderby=views_views), we will create a custom query as needed.
To sort by a meta-field, it is easiest to change the arguments of the base query using the pre_get_posts
hook. But it is important to understand that this hook is global and triggers every time a page is generated, not only in the admin area but also on the front end. Therefore, we must specify precisely when to modify the query, in our case, this will be when the orderby argument equals views_views. In all other cases, we do not touch the query.
Option 1:
// modify the query when sorting the column add_action( 'pre_get_posts', 'add_column_views_request' ); function add_column_views_request( $query ) { if( ! is_admin() || ! $query->is_main_query() || $query->get( 'orderby' ) !== 'views_views' || get_current_screen()->id !== 'edit-post' ){ return; } $query->set( 'meta_key', 'views' ); $query->set( 'orderby', 'meta_value_num' ); }
Option 2: the principle is exactly the same, just using the request hook:
// modify the query when sorting the column add_filter( 'request', 'add_column_views_request' ); function add_column_views_request( $vars ) { if( isset( $vars['orderby'] ) && $vars['orderby'] === 'views_views' ){ $vars['meta_key'] = 'views'; $vars['orderby'] = 'meta_value_num'; } return $vars; }
Option 3: the principle is taken from the article Sortable Taxonomy Columns.
Here we modify the SQL query, not the parameters passed to WP_query. This will be useful if we need to create some unique sorting. I adapted it to our case:
// modify the query when sorting the column add_filter( 'posts_clauses', 'add_column_views_request', 10, 2 ); function add_column_views_request( $clauses, $wp_query ) { if( 'views_views' != $wp_query->query['orderby'] ){ return $clauses; } global $wpdb; $clauses['join'] .= " LEFT JOIN {$wpdb->postmeta} ON {$wpdb->posts}.ID={$wpdb->postmeta}.post_id"; //$clauses['where'] .= " AND {$wpdb->postmeta}.meta_key='views'"; $clauses['orderby'] = " {$wpdb->postmeta}.meta_value+0 "; $clauses['orderby'] .= ( 'ASC' == strtoupper( $wp_query->get( 'order' ) ) ) ? 'ASC' : 'DESC'; // other modifiable elements //$clauses['groupby'] //$clauses['distinct'] //$clauses['fields'] // wp_posts.* //$clauses['limits'] // LIMIT 0, 20 return $clauses; }
Posts with empty meta-fields (it will not exist for the post) will not be included in the selection.
Note
The above options show that when sorting, only those records with the specified meta-field exist. It is complicated to sort so that there are also records where the field does not exist! For this, a separate complex query would need to be written. It is much simpler and more logical to stick with what we get...
For example, doing it like this (or in a similar way) will not work:
Column Width
It may also be useful to edit the column width, as it sometimes stretches unnecessarily. We specify the width like this:
// adjust the column width via css add_action('admin_head', 'add_views_column_css'); function add_views_column_css(){ echo '<style type="text/css">.column-views{ width:10%; }</style>'; }
Full Code (as a Class)
/** * Additional sortable columns for posts in the admin area */ final class My_Sortable_Post_Columns { public static function init() { // create a new column add_filter( 'manage_post_posts_columns', [ __CLASS__, 'add_columns' ], 4 ); // fill the column with data - wp-admin/includes/class-wp-posts-list-table.php add_filter( 'manage_post_posts_custom_column', [ __CLASS__, 'fill_columns' ], 5, 2 ); // adjust the column width via css add_action( 'admin_head', [ __CLASS__, '_css' ] ); // add the ability to sort the column add_filter( 'manage_edit-post_sortable_columns', [ __CLASS__, 'add_sortable_columns' ] ); // modify the query when sorting the column add_filter( 'pre_get_posts', [ __CLASS__, 'handle_sort_request' ] ); } public static function add_columns( $columns ) { // insert in the right place - 3 - 3rd column $out = []; foreach( $columns as $col => $name ){ if( ++$i == 3 ){ $out['views'] = 'Visits'; } $out[ $col ] = $name; } return $out; } public static function add_sortable_columns( $sortable_columns ) { $sortable_columns['views'] = 'views_views'; return $sortable_columns; } public static function fill_columns( $colname, $post_id ) { if( $colname === 'views' ){ echo get_post_meta( $post_id, 'views', 1 ); } } public static function _css() { if( 'edit' === get_current_screen()->base ){ echo '<style>.column-views{ width:10%; }</style>'; } } public static function handle_sort_request( $object ) { if( $object->get( 'orderby' ) != 'views_views' ){ return; } $object->set( 'meta_key', 'views' ); $object->set( 'orderby', 'meta_value_num' ); } }