Custom columns for custom post types

I got a bit inspired by Joost de Valk’s post on custom post type snippets. Joost provided some code snippets for adding custom columns on the edit posts page for custom post types. While the snippets are handy, I wanted to dive into this subject just a bit more.

This tutorial assumes you already know how to create custom post types and taxonomies. If not, you’ll want to check out these tutorials on post types and taxonomies.

This tutorial will walk you through creating custom columns for your post types and making these columns sortable. We’re sorting posts by two different types of data:

  • Metadata: Ordering posts by meta value based on a specific meta key.
  • Taxonomy: Creating links to allow the user to view all posts with a specific term.

The setup

In this tutorial, I’m using a few different things to show how the methods used work:

  • movie: Name of my custom post type.
  • genre: Name of a taxonomy to group movies.
  • duration: Post metadata key (custom field) to represent the length of the movie in minutes.

After adding a few “movies,” the edit posts screen will something look like the following screenshot.

Default edit movies screen in WordPress

That’s pretty bland and boring. The great thing is that WordPress provides us with the tools for making this not so boring. With a few custom functions, we can turn that page into something that looks more like the next screenshot.

Screenshot of edit posts screen

Much prettier, right? We can add relevant information about the posts of our custom post type and even make the posts sortable on that screen using post metadata.

Adding custom columns

The first step in creating custom columns, is to make these columns available on the edit posts page. WordPress has a variable filter hook for this called manage_edit-{$post_type}_columns for all post types. Since the name of our post type is movie, the name of the hook you’d use is manage_edit-movie_columns.

What we’ll do here is overwrite all of the default columns and set it up however we want. In this example, we’re using five columns:

  • cb: The checkbox column.
  • title: The post title column, which we're changing to read "Movie."
  • duration: The column for the duration of the movie (custom metadata).
  • genre: The column for the movie genre (custom taxonomy).
  • date: The default post date column.

Filtering these columns is fairly simple. You only need to return an array of key/value pairs. The key is the column name and the value is the output of the column header/label.

add_filter( 'manage_edit-movie_columns', 'my_edit_movie_columns' ) ;

function my_edit_movie_columns( $columns ) {

	$columns = array(
		'cb' => '<input type="checkbox" />',
		'title' => __( 'Movie' ),
		'duration' => __( 'Duration' ),
		'genre' => __( 'Genre' ),
		'date' => __( 'Date' )
	);

	return $columns;
}

Adding content to custom columns

Now that you’ve created some custom columns, you have to actually put some content in them. We’re only worried about the duration and genre columns because they’re the only custom columns. WordPress knows how to handle the other columns by default.

The action hook in this case is manage_{$post_type}_posts_custom_column. Remember, the name of the post type is movie, so this hook becomes manage_movie_posts_custom_column.

What we’ll do is set up a PHP switch statement in our action to display our custom content when one of our custom columns is called.

  • For the duration metadata, we'll simply get the post meta and display its value or "Unknown" if no data exists.
  • For the genre taxonomy, we'll display the movie's genres and add a link with some query arguments for viewing movies by genre.

I’ve tried to leave the following code fairly well commented so that you can pick it apart and use for your own projects.

add_action( 'manage_movie_posts_custom_column', 'my_manage_movie_columns', 10, 2 );

function my_manage_movie_columns( $column, $post_id ) {
	global $post;

	switch( $column ) {

		/* If displaying the 'duration' column. */
		case 'duration' :

			/* Get the post meta. */
			$duration = get_post_meta( $post_id, 'duration', true );

			/* If no duration is found, output a default message. */
			if ( empty( $duration ) )
				echo __( 'Unknown' );

			/* If there is a duration, append 'minutes' to the text string. */
			else
				printf( __( '%s minutes' ), $duration );

			break;

		/* If displaying the 'genre' column. */
		case 'genre' :

			/* Get the genres for the post. */
			$terms = get_the_terms( $post_id, 'genre' );

			/* If terms were found. */
			if ( !empty( $terms ) ) {

				$out = array();

				/* Loop through each term, linking to the 'edit posts' page for the specific term. */
				foreach ( $terms as $term ) {
					$out[] = sprintf( '<a href="%s">%s</a>',
						esc_url( add_query_arg( array( 'post_type' => $post->post_type, 'genre' => $term->slug ), 'edit.php' ) ),
						esc_html( sanitize_term_field( 'name', $term->name, $term->term_id, 'genre', 'display' ) )
					);
				}

				/* Join the terms, separating them with a comma. */
				echo join( ', ', $out );
			}

			/* If no terms were found, output a default message. */
			else {
				_e( 'No Genres' );
			}

			break;

		/* Just break out of the switch statement for everything else. */
		default :
			break;
	}
}

At this point, you can already click on a movie genre and see all movies that have that specific genre. For example, if I click on the “Science Fiction” genre, I see a list of all the movies within that genre.

Sorting custom post types by taxonomy

Making custom columns sortable

Now that you’ve learned how to add custom columns, it’s time to make them sortable. There’s actually two steps to this process.

  • Telling WordPress that a column is sortable.
  • Actually sorting the content, which we have to do since it's custom.

The end result should look like the following screenshot.

Sorting custom post type columns

Note that we’re only sorting by metadata here, which in our case is uses the duration meta key. The goal is to allow the user to order posts by the duration of the movie in ascending or descending order.

The first step is making the duration column sortable by filtering the manage_edit-{$post_type}_sortable_columns hook as shown in the following code.

add_filter( 'manage_edit-movie_sortable_columns', 'my_movie_sortable_columns' );

function my_movie_sortable_columns( $columns ) {

	$columns['duration'] = 'duration';

	return $columns;
}

The next step is where everything gets a little trickier. We need to add a filter to the request hook but only filter it when viewing this specific screen in the WordPress admin.

What we’ll do is use the load-edit.php hook to make sure we’re on the edit.php screen in the admin. If so, we’ll add our custom filter to request. From there, we’ll run a few checks to see if we need to add anything extra to the posts request.

/* Only run our customization on the 'edit.php' page in the admin. */
add_action( 'load-edit.php', 'my_edit_movie_load' );

function my_edit_movie_load() {
	add_filter( 'request', 'my_sort_movies' );
}

/* Sorts the movies. */
function my_sort_movies( $vars ) {

	/* Check if we're viewing the 'movie' post type. */
	if ( isset( $vars['post_type'] ) && 'movie' == $vars['post_type'] ) {

		/* Check if 'orderby' is set to 'duration'. */
		if ( isset( $vars['orderby'] ) && 'duration' == $vars['orderby'] ) {

			/* Merge the query vars with our custom variables. */
			$vars = array_merge(
				$vars,
				array(
					'meta_key' => 'duration',
					'orderby' => 'meta_value_num'
				)
			);
		}
	}

	return $vars;
}

The two arguments we set for the request are meta_key and orderby. We want WordPress to know that it should display posts with the duration meta key and to order them by their meta value (the actual duration of the movie).

One thing you should note is that I set the orderby to meta_value_num in this particular case. The reason for this is that the meta values are numeric. If not using numeric meta values, you should use meta_value instead.

Have fun with custom columns

Don’t go too crazy with custom columns. You should only add relevant information within your columns. This doesn’t mean add everything imaginable. Think about the relevancy of the column content and decide on whether it should be shown.

I encourage you to play around with the functions in this tutorial though. For example, try selecting a specific genre and then sorting those movies within that genre by duration. It’s pretty cool stuff.