Order by multiple meta keys or meta values in WordPress and WooCommerce

I was recently in a situation where I needed to sort by two different meta keys in WooCommerce. The method I found also works for normal WordPress queries. I did a lot of digging on the internet, but I couldn’t find any article explaining how to do it in a simple way.

What I needed to do was to add a new option to the sorting dropdown on WooCommerce archive pages. This option should then trigger a sort that is first sorted on a meta key for average rating, and then a meta key for the number of ratings. We are using our own review plugin and not WooCommerce standard reviews and ratings.

All products have these meta keys, even if 0, so that they will be present in the query.

Adding a new option to the dropdown is simple:

function trsdm_add_catalog_orderby_by_meta($options)
{
    $options['review'] = 'Sort by reviews';
    return $options;
}
add_filter('woocommerce_catalog_orderby', 'trsdm_add_catalog_orderby_by_meta');Code language: PHP (php)

If we just had to order by a single meta key, the standard way of doing that is this:

trsdm_review_product_sorting($args)
{
    if (isset($_GET['orderby'])) {
        if ('review' === $_GET['orderby']) {
            return [
                'orderby'  => 'meta_value_num',
                'order'    => 'DESC',
                'meta_key' => '_review_average',
            ];
        }
    }

    return $args;
}
add_filter('woocommerce_get_catalog_ordering_args', 'trsdm_review_product_sorting');Code language: PHP (php)

The problem is that this filter only enables us to sort by a single meta key. There is no way to add another meta key to this filter. We have to hook into another hook, namely woocommerce_product_query.

This hook provides a parameter that contains the WP_Query object used for fetching and sorting the products you see on the front end. We can use this code to ensure that we change the query only when needed:

function trsdm_review_sorting_query_set($q)
{

    $order_by = $_GET['orderby'] ?? null;

    if ($order_by !== 'review') {
        return;
    }

    if (is_admin()) {
        return;
    }

    $args = [
        'relation' => 'AND',
        '_review_count' => [
            'key'     => '_review_count',
            'compare' => 'EXISTS',
            'type' => 'NUMERIC',
        ],
        '_review_average' => [
            'key'     => '_review_average',
            'compare' => 'EXISTS',
            'type' => 'DECIMAL(5,1)',
        ]
    ];

    $q->set('meta_query', $args);
    $q->set('orderby', '_review_average _review_count');
    $q->set('order', 'DESC');
}
add_filter('woocommerce_product_query', 'trsdm_review_sorting_query_set');Code language: PHP (php)

Here we change the query before the products are displayed on the front end. We add a meta query where we define _review_count as an integer and _review_average as a decimal number with a range from 0.0 to 5.9 (though we are only using 0.0 – 5.0 when we save the meta value).

Then we set the orderby parameter in the query to order by both meta keys. It’s important to note that you need to name the array keys in the meta_query array and use those names when setting the orderby parameter.

If we were to drop the type in the arrays, we would see some weird sorting results because WordPress would try to sort alphabetically and not numerically. If you have that need, you can of course omit the type or use any other supported type.

This type of code can also be used in normal WP_Query objects that you instantiate yourself when querying different posts in WordPress.

Get these knowledge bombs before they are released

Plus exclusive content only for newsletter family members! No spam or yucky sales tactics. The bombs only drop occasionally for a clutter-free inbox.