Referencing @Otto's response to a question I also had about ordering by multiple fields, here is what he said:

Can't do it with a naive WP_Query. Use the posts_orderby filter to add your own ordering string.

function my_order($orderby) { 
    global $wpdb; 
    return "{$wpdb->posts.post_author} ASC, {$wpdb->posts.post_date} DESC"; 
add_filter( 'posts_orderby', 'my_order' ); 

$blah = new WP_Query(...); 

remove_filter( 'posts_orderby', 'my_order' ); 


This appears to be the way it would be done in a new call to WP_Query --> how would I go about this same thing in a pre_get_posts() action with two meta fields, with a default sort to?:

function mouldings_sort($query) {
    if ($query->is_main_query() && is_tax(array('wood_types','profile_categories','combination_categories'))) {

        $query->set('meta_key', '_mouldings_dimensions_height');
        $query->set('order', 'DESC');


I had previously tried simply adding in another meta field like so:

$query->set('meta_key', array('_mouldings_dimensions_height', '_mouldings_dimension_width'));

with a default sortback of title as so:

 $query->set('orderby','meta_value_num title');

but it doesn't look like meta_key can accept arrays and my title fallback goes back to Otto's original response on the matter. Any help would be a greatly appreciated. Thanks!


2 Answers 2


Never forget that there're actually two filters

// Add additional query args that are allowed by the API by default
// Modify the query herself
// Inspect the resulting string - test this one in for e.g. phpMyAdmin

So everything you can achieve using the pre_get_posts filter should be done there. The rest should be modified using the posts_clauses (or one of the more specific filters before).

// Modify the original query parts
function wpse70214_pre_get_posts( $query )
    var_dump( $query );
    return $query;
add_filter( 'pre_get_posts', 'wpse70214_pre_get_posts' );

// Modify the resulting query string parts
function wpse70214_posts_clauses( $pieces )
    var_dump( $pieces );
    return $pieces;
add_filter( 'posts_clauses', 'wpse70214_posts_clauses' );

// Then check the result
function wpse70214_posts_request( $query_string )
    var_dump( $query_string );
    return $query_string;
add_action( 'posts_request', 'wpse70214_posts_request' );

Yes like Otto said you can't have a secondary ORDER BY clause without a a custom posts_orderby filter. If you need to know what query your on a la "pre_get_posts" you can create a function that adds the orderby filter to posts_orderby and call it from pre_get_posts.

 * Posts orderby filter.  The filter will be added using pre_get_posts outside the class
 *  using the pre_get_posts action allows us to do checks for what page etc...
 * @return string, new MySQL ORDER BY clause
function wpse_order_by() {
    global $wpdb;
    return $wpdb->prepare( "$wpdb->postmeta.meta_value+0 DESC, post_title DESC" );

 * Pre get posts filter for adding secondary fall back ORDER BY clause to MySql query
 * @uses remove_filter(), This filter removes itself after it runs to prevent it from affecting  other queries on the same page.
 * @uses add_filter()
 * @param object|array $query the current $query object
function wpse_post_order_pre( $query ) {
    if ($query->is_main_query() && is_tax(array('wood_types','profile_categories','combination_categories'))) {

    /** remove_filter() is used to prevent this affecting additional queries on the page */
    remove_filter( current_filter(), __FUNCTION__ );

    add_filter( 'posts_orderby', 'wpse_order_by' );

add_action( 'pre_get_posts', 'wpse_post_order_pre' );
  • Hi Chris - I really appreciate the reply. Here is the entire pre_get_posts() filter I am current using. Did I add that in correctly? Currently getting the following DB error on an unrelated WP_Query (which is odd since I'm running a check to make sure we're only effecting the main query). Any thoughts on that? In addition, does this address being able to add-in a secondary order as another meta field, _mouldings_dimensions_width and THEN eventually fall-back to the title? Thanks again for all the help.
    – Zach
    Oct 23, 2012 at 18:57
  • It needs to be in its' own function instead of mixed with your current pre_get_posts. If the remove_filter doesn't run the action will get added to every query on the page. You will get an unknown column postmeta.meta_value MySql error if it runs on any query that is NOT a meta_query because it needs the INNER join on postmeta to work. Use debug bar so you can see what the MySql looks like to debug this.
    – Chris_O
    Oct 23, 2012 at 19:31
  • Thanks Chris - SQL error on the page is SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'galleries' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private') ORDER BY wp_postmeta.meta_value+0 DESC, post_title DESC LIMIT 0, 2 which means it's still reaching outside of the main query. Would this be more the plugin adding the galleries post type not resetting the query correctly? Any docs on what meta_value+0 is, I'll obviously take the answer ;) but would love to know what that is as well. Thanks.
    – Zach
    Oct 23, 2012 at 19:49
  • That query, shown here does appear to reset the query properly (generating from a widget).
    – Zach
    Oct 23, 2012 at 19:51
  • Those errors are because remove_filter( current_filter(), FUNCTION ); is not working or is being fired to early. You can try setting the priority to 99999 to make sure it fires last.
    – Chris_O
    Oct 23, 2012 at 20:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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