When editing one of my custom post types I want to be able to list all entries by a custom field instead of the date they are published (which, for a custom post type probably isn't relevant). I got a lead from the comments of a blog post about custom post types and the author said it was possible and that he even made it so you could click on the column names for a custom sort. He mentioned the posts_orderby function which I noted in my own comments but now I can find the blog post anymore. Any suggestions? I saw one solution that used

add_action('wp', 'check_page');

And the check_page function used add_filter to change the query but I'm pretty sure it would only work in the theme files, not in the admin area.


3 Answers 3


As you can probably imagine by the lack of answers provided, the solution is not exactly trivial. What I've done is create a somewhat self-contained example that assumes a custom post type of "movie" and custom field key of "Genre".

Disclaimer: this works with WP3.0 but I can't be sure it will work with earlier versions.

You basically need to hook two (2) hooks to make it work and another two (2) to make it obvious and useful.

The first hook is 'restrict_manage_posts' which lets you emit an HTML <select> in the area above the list of posts where the "Bulk Actions" and "Show Dates" filters. The code provided will generate the "Sort by:" functionality as seen in this screen snippet:

How to Create Sort By functionality for a Custom Post Type in the WordPress Admin
(source: mikeschinkel.com)

The code uses direct SQL because there is not a WordPress API function to provide the list of all meta_keys for a post types (sounds like a future trac ticket to me...) Anyway, here's the code. Note that it grabs the post type from $_GET and validates to make sure it is both a valid post type post_type_exists() as well as being a movie post type (those two checks are overkill but I did it to show you how if you don't want to hard-code the post type.) Lastly I use the sortby URL parameter as it doesn't conflict with anything else in WordPress:

function restrict_manage_movie_sort_by_genre() {
    if (isset($_GET['post_type'])) {
        $post_type = $_GET['post_type'];
        if (post_type_exists($post_type) && $post_type=='movie') {
            global $wpdb;
SELECT pm.meta_key FROM {$wpdb->postmeta} pm
INNER JOIN {$wpdb->posts} p ON p.ID=pm.post_id
WHERE p.post_type='movie' AND pm.meta_key='Genre'
GROUP BY pm.meta_key
ORDER BY pm.meta_key
            $results = $wpdb->get_results($sql);
            $html = array();
            $html[] = "<select id=\"sortby\" name=\"sortby\">";
            $html[] = "<option value=\"None\">No Sort</option>";
            $this_sort = $_GET['sortby'];
            foreach($results as $meta_key) {
                $default = ($this_sort==$meta_key->meta_key ? ' selected="selected"' : '');
                $value = esc_attr($meta_key->meta_key);
                $html[] = "<option value=\"{$meta_key->meta_key}\"$default>{$value}</option>";
            $html[] = "</select>";
            echo "Sort by: " . implode("\n",$html);

The second required step is to use the parse_query hook that is called after WordPress decides one what query is should run but before it runs the query. Here we get to set values of orderby and meta_key in the query's query_var array which are documented in the Codex in the orderby parameter for query_posts(). We test to make sure that:

  1. We are in the admin (is_admin()),
  2. We are on the page that lists posts in the admin ($pagenow=='edit.php'),
  3. The page has been called with a post_type URL parameter equal to movie, and
  4. The page has also been called with a sortby URL parameter and that it wasn't passed a value of 'None'

If all those tests pass we then set the query_vars (as documented here) to meta_value and our sortby value for 'Genre':

add_filter( 'parse_query', 'sort_movie_by_meta_value' );
function sort_movie_by_meta_value($query) {
    global $pagenow;
    if (is_admin() && $pagenow=='edit.php' &&
        isset($_GET['post_type']) && $_GET['post_type']=='movie' && 
        isset($_GET['sortby'])  && $_GET['sortby'] !='None')  {
        $query->query_vars['orderby'] = 'meta_value';
        $query->query_vars['meta_key'] = $_GET['sortby'];

And that's all you need to do; no "posts_order" or "wp" hooks required! Of course you actually do need to do more; you need to add some columns on your page that lists the posts so you can actually see the values that it is sorting by otherwise the users will get mucho confused. So add a manage_{$post_type}_posts_columns hook, in this case manage_movie_posts_columns. This hook gets passed the default array of columns and for simplicity I just replaced it with two standard columns; a checkbox (cb) and a post name (title). (You can inspect posts_columns with a print_r() to see what else is available by default.)

I decided to add a "Sorted By:" for when there is a sortby URL parameter and when it is not None:

add_action('manage_movie_posts_columns', 'manage_movie_posts_columns');
function manage_movie_posts_columns($posts_columns) {
    $posts_columns = array(
        'cb' => $posts_columns['cb'],
        'title' => 'Movie Name',
    if (isset($_GET['sortby']) && $_GET['sortby'] !='None') 
        $posts_columns['meta_value'] = 'Sorted By';

    return $posts_columns;

Finally we use the manage_pages_custom_column hook to actually display the value when there is a post of the appropriate post type and with the probably redundant test for is_admin() and $pagenow=='edit.php'. When there is a sortby URL parameter we extract the custom field value that is being sorted by an display it in our list. Here's what it looks like (remember, this is test data so no comments from the peanut gallery on the movie classifications! :):

Custom Columns added for a Custom Post Type in the WordPress Admin
(source: mikeschinkel.com)

And here is the code:

add_action('manage_pages_custom_column', 'manage_movie_pages_custom_column',10,2);
function manage_movie_pages_custom_column($column_name,$post_id) {
    global $pagenow;
    $post = get_post($post_id);
    if ($post->post_type=='movie' && is_admin() && $pagenow=='edit.php')  {
        switch ($column_name) {
            case 'meta_value':
                if (isset($_GET['sortby']) && $_GET['sortby'] !='None') {
                    echo get_post_meta($post_id,$_GET['sortby'],true);

Note that this only picks up the first "Genre" for a movie, i.e. the first meta_value in the case of multiple values for a given key. But then again I'm not sure how it would work otherwise!

And for those unfamiliar with where to put this code you can put it in a plugin or more likely for the newbie in the functions.php file in your current theme.

How this helps.

  • 2
    +1, just for the effort. But it would be even better if the circles were hand-drawn :-)
    – Jan Fabry
    Commented Aug 12, 2010 at 7:34
  • Any idea how to remove the SHOW ALL DATES filter completely so that only my custom filters are showing for a given post type? Commented May 22, 2014 at 17:02
  • @RailsTweeter Use the technique I showed here where the two hooks that bracket the HTML generation are 'months_dropdown_results' and 'restrict_manage_posts'. P.S. Upvotes are always appreciated. :) Commented May 22, 2014 at 23:56
  • @MikeSchinkel , now that there is a WP API, will this update your code somewhat?
    – samjco-com
    Commented Feb 16, 2019 at 20:23
  • @samjco Not sure. I unfortunately don't have the time to revise this at the moment. Commented Feb 17, 2019 at 2:53

As of WordPress 3.1 (I'm using the beta) columns can now be sortable via their titles.

The following post details how implement them.



Here's a simple solution:

/* --------Sortable Events on Dashboard - show start date, time, venue--------- */

    Custom Columns

function my_*YOUR POST TYPE*_columns($columns)
    $columns = array(
        'cb'        => '<input type="checkbox" />',
        'title'     => 'Title',
        'your_custom_field'     => 'Custom Field Name',          
        'date'      =>  'Date',
    return $columns;

function my_custom_columns($column)
    global $post;
    if($column == 'your_custom_field')
        if(get_post_meta($post->ID, 'your_custom_field', true);)
            echo get_post_meta($post->ID, 'your_custom_field', true);


add_action("manage_posts_custom_column", "my_custom_columns");
add_filter("manage_edit-*YOUR POST TYPE*_columns", "my_events_columns");

    Sortable Columns

function my_column_register_sortable( $columns )
    $columns['your_custom_field'] = 'your_custom_field';
    return $columns;

add_filter("manage_edit-*YOUR POST TYPE*_sortable_columns", "my_column_register_sortable" );

Just replace YOUR POST TYPE and 'your_custom_field'

Not the answer you're looking for? Browse other questions tagged or ask your own question.