WordPress custom sort order

I’ve been battling recently with trying to get a custom sort order to work in WordPress, and despite a lot of Googling I found it really hard to find an answer that worked.

I love Advanced Custom Fields, it’s a fantastic WordPress plugin for adding custom fields to pretty much anything in WordPress, but I typically use it for specific pages and posts of a specific custom post type.

Some fields (but certainly not all) are really useful to display in the table view on the summary screen for that post type.  For example, I was trying to add a numeric “Download Count”, and adding the column goes something like this…

function custom_edit_SLUG_columns($columns) {
  $columns['download_count'] = 'Download Count'; //translate this
  return $columns;
}
add_filter('manage_SLUG_posts_columns', 'custom_edit_SLUG_columns');

Where I’ve used “SLUG” this should be replaced with the slug of the custom post type that has been added.  You can override this array as well, if you want to change the column order, or remove items if you don’t want columns displayed. But for my purposes, I only needed to add a new column.

Then if you want to make the column sortable (which is probably most of the time) then you need to add something like this…

function custom_edit_SLUG_sortable_columns($columns) {
  $columns['download_count'] = 'download_count';
  return $columns;
}
add_filter('manage_edit-SLUG_sortable_columns', 'custom_edit_SLUG_sortable_columns');

function custom_orderby($query) {
  if(!is_admin() || !$query->is_main_query()) {
    return;
  }
  $orderby = $query->get('orderby');
  if ('download_count' == $orderby) {
    $query->set('meta_key', 'download_count');
    $query->set('orderby', 'meta_value_num');
  }
}
add_action('pre_get_posts', 'custom_orderby');

However, as there were existing posts before I added the new custom field “download_count”, there are a lot of blank values. And as it’s a numeric field, I’d rather it displayed 0 instead of blank.  You can do that like this…

function custom_SLUG_column($column, $post_id) {
  if('download_count' == $column) {
    $val = get_post_meta($post_id, $column, true);
    if($val==='') {
      echo '0';
    }
    else {
      echo $val;
    }
  }
}
add_action('manage_SLUG_posts_custom_column', 'custom_SLUG_column', 10, 2);

I got this far without any problems, but then I got stuck.  Everything displayed as I wanted, but as soon as I clicked the header to sort, only the posts with the value populated appeared, the rest had gone.

The solution to this is to change the “custom_orderby” function to use a meta query…

function custom_orderby($query) {
  if(!is_admin() || !$query->is_main_query()) {
    return;
  }
  $orderby = $query->get('orderby');
  if ('download_count' == $orderby) {
    $meta_query = array(
      'relation' => 'OR',
      array(
        'key' => 'download_count',
        'compare' => 'EXISTS',
      ),
      array(
        'key' => 'download_count',
        'compare' => 'NOT EXISTS',
      )
    );
    $query->set('meta_query', $meta_query);
    $query->set('orderby', 'meta_value_num');
  }
}
add_action('pre_get_posts', 'custom_orderby');

Essentially this is saying that it should return posts where the new custom field is populated (‘EXISTS’) or isn’t populated (‘NOT EXISTS’), which covers the bases and means that all posts are returned. Sorting numerically (using ‘meta_value_num’) works as I want with blank values, in that they are ordered below 1.

Initially I had left in the “meta_key” from the earlier version of the function, but this needs to be removed for it to work.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.