How to filter or sort a WP_Query by one or more custom fields
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' ]