Sorting Query Results by Multiple Meta Keys

I’m building a website that features reviews of hosting companies. I’m storing Price, Rating, and “Top Pick” as post meta. Users can sort the results by Price, Rating and Post Date.

The client wants Top Picks to always show up first. So if they select it to sort by Price (Low to High), it will first display all the Top Picks sorted by price low to high, then the other reviews sorted by price low to high.

WordPress 4.0 allowed you to specify multiple orderby parameters and set the order of each independently (details here). But you could still only have one based on metadata since it requires the ‘meta_key’ parameter. For example:

<?php
$loop = new WP_Query( array(
'meta_key' => 'be_rating',
'orderby' => array(
'meta_value_num' => 'DESC',
'post_date' => 'ASC',
),
) );
view raw functions.php hosted with ❤ by GitHub

Luckily WordPress 4.2 takes this even further, so now we can easily order by multiple meta keys. Here’s the details (thanks Brian Krogsgard for reminding me of this). Here’s the code I’m using for the sort options in the screenshot above.

<?php
// Query Arguments
$args = array(
'post_type' => 'review',
'posts_per_page' => 10,
'paged' => get_query_var( 'paged', false ),
'meta_query' => array(
'relation' => 'AND',
'be_top_pick' => array(
'key' => 'be_top_pick',
'compare' => 'EXISTS',
),
'be_price' => array(
'key' => 'be_price',
'type' => 'NUMERIC',
'compare' => 'EXISTS',
),
'be_rating' => array(
'key' => 'be_rating',
'type' => 'NUMERIC',
'compare' => 'EXISTS',
),
)
);
// Sort Results
$current_sort = isset( $_GET['hosting-sort'] ) ? esc_attr( $_GET['hosting-sort'] ) : 'most-recent';
switch( $current_sort ) {
case 'most-recent':
$args['orderby'] = array(
'be_top_pick' => 'DESC',
'post_date' => 'DESC',
);
break;
case 'price-high':
$args['orderby'] = array(
'be_top_pick' => 'DESC',
'be_price' => 'DESC',
);
break;
case 'price-low':
$args['orderby'] = array(
'be_top_pick' => 'DESC',
'be_price' => 'ASC',
);
break;
case 'rating-high':
$args['orderby'] = array(
'be_top_pick' => 'DESC',
'be_rating' => 'DESC',
);
break;
case 'rating-low':
$args['orderby'] = array(
'be_top_pick' => 'DESC',
'be_rating' => 'ASC',
);
break;
}
$loop = new WP_Query( $args );

I start by building out the standard query arguments (post type, posts per page…). I then include a meta query for every key I’m using to sort. I have 'compare' => 'EXISTS' since I want posts that have those keys but don’t want to limit it to certain values. Each of my meta queries has a key associated with it. For the numerical metadata I’m specifying 'type' => 'NUMERIC' so that when I sort using it, it uses ‘meta_value_num’ instead of ‘meta_value’ (more info).

In the theme file, the dropdown menu adds a GET variable for specifying the sort order. So I look for the GET variable, and if it isn’t set I use the default sorting method, most recent. Then depending on which sort order is used, I update the $args to include the relevant ‘orderby’ parameters. For the meta queries, use the key you assigned to that query. You can also use any other sort method.

Bill Erickson

Bill Erickson is the co-founder and lead developer at CultivateWP, a WordPress agency focusing on high performance sites for web publishers.

About Me
Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk

Reader Interactions

Comments are closed. Continue the conversation with me on Twitter: @billerickson

Comments

  1. James Maabadi says

    I’ve hacked together a solution to do this before, your’s is much more elegant than what I had. Thanks, this will be very useful!

  2. Felicia Betancourt says

    Thanks so much for this valuable code sample. I looked at WordPress’ page for media_query, and nowhere do they make it clear that this is possible.

  3. Donna French says

    I just found your site today and want to thank you for taking the time to keep it updated!!! I’ve been doing web development for 15+ years and have just started really digging into WordPress dev. There seems to be a significant gap between beginner/fairly intermediate tasks and advanced so I am wondering if there are resource(s) that you recommend?

    Just in case you or anyone has time to get in touch or post here: I need to pin some posts to the top of my results within a post grid & am having trouble wrapping my head around things with the site setup. It is using Visual Composer and The Events Calendar so I’m getting tangled up in where exactly to customize things. Any suggestions welcome.

  4. Alexander says

    Sorry for stupid question but i try to understand how can i handle with this code.

    As I read before, I should change default $query by pre_get_post to get opportunity to sort post by meta value. But in this example no such thing. I even don’t understand is that template part of particular page or it should be inserted to function.php.

    Sorry for my newbiety

    • Bill Erickson says

      The code above is for a custom page template, which is why it’s using a custom WP_Query.

      This could easily be applied to the main query though using pre_get_posts.

  5. Ravi says

    I have created custom sorting option by stock date (which means when I change the status outstock to instock) it will give the product list by modified date. I got the list by using this code:

    $args[‘orderby’] = array(
    ‘meta_value_num’ => ‘DESC’,
    ‘title’ => ‘ASC’,
    );
    $args[‘meta_key’] = ‘_stock’;
    I would like to sort by stock update with menu_order, so I tried this:

    $args[‘orderby’] = array(
    ‘meta_value_num’ => ‘DESC’,
    ‘title’ => ‘ASC’,
    ‘menu_order’ => ‘ASC’,
    );
    $args[‘meta_key’] = ‘_stock’;
    But it doesn’t list properly. Is that possible to pass menu_order with meta_keys

    Can anyone help me, please? It would be appreciated.

    • Bill Erickson says

      Right now you’re ordering first by meta_value_num, then by title, then by menu_order. So the only way menu_order is used is if two products have the same title. I think you want to change it to:

      $args[‘orderby’] = array(
      ‘meta_value_num’ => ‘DESC’,
      ‘menu_order’ => ‘ASC’,
      ‘title’ => ‘ASC’,
      );
      
  6. R. Stephenson Price says

    Mother of god.

    If I’d had this multiple meta_key subsorting three years ago I would’ve saved weeks of SQL work.

    Thank you for enlightening me.

  7. Kosta Kondratenko says

    This is cool – but what if i want to order by 2 meta keys – one of which only exists in let’s say 10% of the posts – but I want to display ALL the posts but show the posts where the meta key exists at the top. Does that make sense?

    I’ve tried ordering by a meta key but it only shows those posts that have the meta key in them.

    ‘orderby’ => ‘meta_value_num’,
    ‘meta_key’ => ‘_adforest_is_feature’,

    This only shows those posts that have that meta key – but I want to order first by the posts that have the meta key as 1 and then all posts that have it either as 0 or don’t have it at all.

    • Bill Erickson says

      I’d try doing a meta query if that key exists OR doesn’t exist (which would then be a query for all posts). You should then be able to orderby that key I think.

  8. Faisal says

    How to sort post when meta key and value save as comment meta ? because my rating data save as comment meta .
    I want to short post by them.

    • Bill Erickson says

      You can’t query posts based on comment metadata, sine that is associated with the comment object and not the post object.

      The way most rating plugins work is storing the individual rating as comment meta, and saving an aggregate rating and total ratings as post meta. You can then query by aggregate rating.

  9. Ali Abbas says

    I’d try doing a meta query if that key exists OR doesn’t exist (which would then be a query for all posts). You should then be able to order by that key I think.