How to filter custom post type by meta field

thomas martinsen 29267

By default, WordPress allows you to filter posts and pages by a couple of different parameters: namely, by publication and by category. Let’s say though that you’ve added a new meta field to your post or you’ve even created a whole new post type with all kinds of new meta fields. It would be nice to be able to filter your post type by one or more of those meta fields.

Standard filters on posts

What is a meta field?

Yes, good question. A meta field is a piece of custom data in addition to the main post content. For example, you’ve got a custom post type called ‘Book’ and you’ve created a meta field called ‘Genre’ where you can define each book’s genre.

I am assuming that you already know how to add meta boxes and meta fields to your content. If not, there’s some more information at https://developer.wordpress.org/reference/functions/add_meta_box/ and you don’t have to look very far for a number of different libraries and tools that will allow you to create the necessary code quickly and easily.

In my Wisdom plugin, I use a lot of different meta fields to store data about tracked websites. Wisdom is a plugin for plugin developers – you add a code snippet to your plugin and it will send back data about every site that uses that plugin. Every site is saved as a custom post type and typical meta fields include the plugin slug, the site name where the plugin is being used, the status of the plugin, and so on.

As data started to build up for the plugins I was tracking, I found I needed a way to filter the list of tracked sites according to what plugins were installed: for example, if I just wanted to view sites that had the Discussion Board Pro plugin installed.

Adding a custom filter dropdown

The first step in creating the custom filter is to build a new select field, or dropdown box, that contains all the possible values for the meta field. This is done via the restrict_manage_posts filter:

/**
 * Filter slugs
 * @since 1.1.0
 * @return void
 */
function wisdom_filter_tracked_plugins() {
  global $typenow;
  global $wp_query;
    if ( $typenow == 'tracked-plugin' ) { // Your custom post type slug
      $plugins = array( 'uk-cookie-consent', 'wp-discussion-board', 'discussion-board-pro' ); // Options for the filter select field
      $current_plugin = '';
      if( isset( $_GET['slug'] ) ) {
        $current_plugin = $_GET['slug']; // Check if option has been selected
      } ?>
      <select name="slug" id="slug">
        <option value="all" <?php selected( 'all', $current_plugin ); ?>><?php _e( 'All', 'wisdom-plugin' ); ?></option>
        <?php foreach( $plugins as $key=>$value ) { ?>
          <option value="<?php echo esc_attr( $key ); ?>" <?php selected( $key, $current_plugin ); ?>><?php echo esc_attr( $key ); ?></option>
        <?php } ?>
      </select>
  <?php }
}
add_action( 'restrict_manage_posts', 'wisdom_filter_tracked_plugins' );

There are some important things to note in this code:

  • Firstly, note that the slug for my custom post type is tracked-plugin. You’ll need to update this to match the slug for your custom post type.
  • The $plugins array is where I’m putting the values that will appear in the dropdown filter. I’ve created the $plugins array artificially – by manually populating it with the slugs of some of the plugins I’m tracking. If you are modifying this code for your own use, you’ll have to create your own array with the values of the meta field you want to filter. The best way to do this might be to run a custom query on the meta field to grab all the possible values then populate the array.
  • We check for the slug parameter in the URL. Again, you’ll need to change this parameter to one that matches the name of your meta field.
  • We create a select field with a name that matches the $_GET parameter we checked, in this case slug.
  • The first option is ‘all’, which means no filtering takes place and all posts are returned
  • Then we iterate through the $plugins array to create an option for each of the values in $plugins. We use the selected method to check if the option matches the $current_plugin parameter so that the correct option is displayed in the dropdown after the page reloads

The result should look something like this:

Updating the query

The second stage of the process uses the parse_query filter to update the query:

/**
 * Update query
 * @since 1.1.0
 * @return void
 */
function wisdom_sort_plugins_by_slug( $query ) {
  global $pagenow;
  // Get the post type
  $post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : '';
  if ( is_admin() && $pagenow=='edit.php' && $post_type == 'tracked-plugin' && isset( $_GET['slug'] ) && $_GET['slug'] !='all' ) {
    $query->query_vars['meta_key'] = 'wisdom_plugin_slug';
    $query->query_vars['meta_value'] = $_GET['slug'];
    $query->query_vars['meta_compare'] = '=';
  }
}
add_filter( 'parse_query', 'wisdom_sort_plugins_by_slug' );

This function contains quite a long conditional statement that checks the following:

  • Are we in an admin page
  • Is the current page edit.php
  • Is the post type correct, i.e. tracked-plugin
  • Has the slug parameter been set

If all these conditions are met, it modifies the query using a meta query. The parameters are:

  • meta_key – this should be the name of the meta field you are filtering on
  • meta_value – this is the value you’re going to use in the filter. This should match the name of your select field.
  • meta_compare – only return results that equal the specified meta_value

For more information about meta queries, you can take a look at this article.

Now, with the two functions added and up and running, you can filter on your meta field:

Final thoughts

Hopefully, this should be a pretty simple way to add a new filter dropdown to your custom post type. Remember, it only really makes sense to do this if your meta field has a relatively small number of possible values.

Two comments

  1. User image

    Hi Gareth!

    Thank you for the information. It’s been really really useful.

    I just had to change all $key’s for $value here:

    <option value="” >

    so the dropdown doesn’t show “0”, “1”, etc ($key), but the $value asociated to the $key.

  2. User image

    Looks like you need one more check to make sure you’re editing the correct query (I got caught out by this), otherwise great!

    if(isset( $query->query[‘post_type’] ) && $query->query[‘post_type’] == $post_type ) {

Leave a Reply

Your email address will not be published. All fields are required.