If you’re familiar with WP_Query, you’ve probably used the meta_query parameter to filter results by one or more custom field values.

But did you know that you can also use meta_query to sort the results, if you give the meta_query segment a named key? This feature was introduced in WordPress 4.2.

Below are examples of both applications. If you have trouble writing your custom meta queries, this WP_Meta_Query generator could be useful.

Filter a query by a custom field

The following example uses the meta_query parameter to filter the query results by one or more custom field values:

How to use custom PHP code?

PHP code can be added to your (child) theme's functions.php file. Alternatively, you can use the Custom Hooks add-on, or a code snippets plugin. More info

$args = [ 'post_type' => 'cars', 'post_status' => 'publish', 'meta_query' => [ [ 'key' => 'is_featured', 'compare' => 'EXISTS' ] ] ]; // Run the query $query = new WP_Query( $args );

Order a query by a custom field

The following example uses the meta_query parameter to order the query results by one or more custom field values. In this snippet, we are using featured as the meta_query segment name. It can then be use within the orderby clause:

How to use custom PHP code?

PHP code can be added to your (child) theme's functions.php file. Alternatively, you can use the Custom Hooks add-on, or a code snippets plugin. More info

$args = [ 'post_type' => 'cars', 'post_status' => 'publish', 'meta_query' => [ 'featured' => [ 'key' => 'is_featured', 'compare' => 'EXISTS' ] ], 'orderby' => [ 'featured' => 'DESC' ] ]; // Run the query $query = new WP_Query( $args );

Order a query by multiple custom fields

To sort by multiple custom fields, you have to use multiple meta_query keys. In the following example, we first sort by featured, then by rating:

How to use custom PHP code?

PHP code can be added to your (child) theme's functions.php file. Alternatively, you can use the Custom Hooks add-on, or a code snippets plugin. More info

$args = [ 'post_type' => 'cars', 'post_status' => 'publish', 'meta_query' => [ 'featured' => [ 'key' => 'is_featured', 'compare' => 'EXISTS' ], 'rating' => [ 'key' => 'avg_rating', 'compare' => 'EXISTS', 'type' => 'numeric' ] ], 'orderby' => [ 'featured' => 'DESC', 'rating' => 'DESC' ] ]; // Run the query $query = new WP_Query( $args );

Order a query by multiple custom fields that can be empty for some posts

If you want to order a query by a custom field, and that field does not exist for some posts, these posts will not appear in the listing. To prevent this, you can set up the arguments as follows, without named segments (like in the previous example).

The following query first sorts by the custom field, then by post title. Posts without the custom field will be sorted by post title and added after the ones with the custom field:

How to use custom PHP code?

PHP code can be added to your (child) theme's functions.php file. Alternatively, you can use the Custom Hooks add-on, or a code snippets plugin. More info

$args = [ 'post_type' => 'cars', 'post_status' => 'publish', 'meta_query' => [ 'relation' => 'OR', [ 'key' => 'my_custom_field', 'compare' => 'NOT EXISTS' ], [ 'key' => 'my_custom_field', 'compare' => 'EXISTS' ] ], 'orderby' => [ 'meta_value_num' => 'DESC', // Sort by 'my_custom_field' first. Use DESC to show posts with the field existing first. Use ASC to show posts with the field existing last. Use 'meta_value' instead if the custom field does not contain numeric values. 'title' => 'ASC' // Sort by post title A-Z ], 'meta_key' => 'my_custom_field', ]; // Run the query $query = new WP_Query( $args );

Make sure to use meta_value in line 16 if the custom field does not contain numeric values.

It’s important that the NOT EXISTS comparison is before the EXISTS comparison. See this StackExchange question for more info and examples.

Also be aware that ordering with named meta_query keys does not work in this setup.

Order a query by multiple custom fields that can be empty for some posts – alternative approach

In some situations, the above way to order posts by existing and non-existing custom field values will cause issues with the desired sort order (as set in the orderby argument) for the posts with an existing custom field value.

For example, if you want these posts ordered by the value of that same (existing) field, in ASC order. Because of the existing, required DESC order for the same field, this will not work. To fix this, you can use the below alternative approach. It uses two separate custom queries that use WP’s get_posts() function, and merges the returned post ID arrays into the post__in query parameter of the main query:

How to use custom PHP code?

PHP code can be added to your (child) theme's functions.php file. Alternatively, you can use the Custom Hooks add-on, or a code snippets plugin. More info

// Get all post IDs of posts with a value for the deadline field $deadline = get_posts( [ 'fields' => 'ids', 'post_type' => [ 'subsidy' ], 'post_status' => [ 'publish' ], 'meta_query' => [ 'deadline' => [ 'key' => 'deadline', 'value' => '', 'compare' => '!=', 'type' => 'CHAR' ], ], 'orderby' => [ 'deadline' => 'ASC', // Primary ASC sort 'date' => 'ASC' // Secondary sort to ensure order consistency ], 'posts_per_page' => - 1 ] ); // Get all post IDs of posts without a value for the deadline field or with a non-existing deadline field $no_deadline = get_posts( [ 'fields' => 'ids', 'post_type' => [ 'subsidy' ], 'post_status' => [ 'publish' ], 'meta_query' => [ 'relation' => 'OR', 'empty_deadline' => [ 'key' => 'deadline', 'value' => '', 'compare' => '==', 'type' => 'CHAR' ], 'no_deadline' => [ 'key' => 'deadline', 'compare' => 'NOT EXISTS' ], ], 'orderby' => [ 'date' => 'ASC' // Secondary sort to ensure order consistency ], 'posts_per_page' => - 1 ] ); $args = [ 'post_type' => 'subsidy', 'post_status' => 'publish', 'post__in' => array_merge( $deadline, $no_deadline ), 'orderby' => 'post__in', 'posts_per_page' => 10, ]; // Run the query $query = new WP_Query( $args );

Improve meta_query performance

When using meta_query, look out for the following scenarios that can negatively impact query performance:

Avoid querying by specific meta values

The following meta_query is potentially very slow, because in WP the key column is indexed, but the value column is not. As the wp_postmeta table can have millions of columns, a lookup of a value can take seconds. So where possible, try to avoid doing this:

How to use custom PHP code?

PHP code can be added to your (child) theme's functions.php file. Alternatively, you can use the Custom Hooks add-on, or a code snippets plugin. More info

'meta_query' => [ [ 'key' => 'stars', 'value' => '5' ] ]

This is much faster:

How to use custom PHP code?

PHP code can be added to your (child) theme's functions.php file. Alternatively, you can use the Custom Hooks add-on, or a code snippets plugin. More info

'meta_query' => [ [ 'key' => 'stars_5', 'compare' => 'EXISTS' ] ]

This will only work if your values are true/false or if you only have a limited number of possible values, for which EXISTS means true and NOT EXISTS means false.

Be careful with relative date/time offsets

Less common, but be careful using relative date/time offsets. For example, this date_query is potentially very slow:

How to use custom PHP code?

PHP code can be added to your (child) theme's functions.php file. Alternatively, you can use the Custom Hooks add-on, or a code snippets plugin. More info

'date_query' => [ 'after' => '2 days ago' ]

Internally this does strtotime('2 days ago'); to calculate the offset to the current time, which is a different value one second later. So each time in the loop of the query, this value is going to be different, and the query cache key will be different, making the cache very inefficient.

Instead, you can do this, which is a static value for 24 hours:

How to use custom PHP code?

PHP code can be added to your (child) theme's functions.php file. Alternatively, you can use the Custom Hooks add-on, or a code snippets plugin. More info

'date_query' => [ 'after' => '2 days ago at midnight' ]

See also

Last updated: September 10, 2024