The meta_query parameter in WP_Query lets you filter WordPress posts by their custom field values. If you use Advanced Custom Fields or store data in post meta, meta_query is how you retrieve exactly the posts you need.
This guide covers the simple shorthand syntax, querying by single and multiple meta values, nested conditions, date ranges, LIKE searches, comparison operators, the type parameter, EXISTS/NOT EXISTS checks, and sorting by one or multiple custom fields. You’ll want to be familiar with PHP and the basics of adding code snippets to WordPress.
meta_query Structure at a Glance
'meta_query' => array(
'relation' => 'AND', // AND or OR (optional, default AND)
array(
'key' => 'field_name', // required
'value' => 'field_value', // optional for EXISTS/NOT EXISTS
'compare' => '=', // optional, default '='
'type' => 'CHAR', // optional, default 'CHAR'
),
// additional clauses...
)
Each clause inside meta_query accepts four parameters: key (the custom field name), value (what to match), compare (the operator), and type (how to cast the value). Only key is required.
Simple meta_key and meta_value Query
For basic queries with a single custom field, you don’t need the full meta_query array. WP_Query accepts meta_key, meta_value, and meta_compare as top-level parameters:
$args = array(
'post_type' => 'post',
'meta_key' => 'featured',
'meta_value' => 'yes',
'meta_compare' => '=',
);
$query = new WP_Query( $args );
This shorthand works for simple cases. For anything involving multiple fields, nested conditions, or the type parameter, use the meta_query array shown in the sections below.
Query Posts By a Meta Value
The most common use case is filtering posts by a single custom field value.
For example, say your posts have a custom field called “featured” that controls which posts appear in a featured section on your homepage.
| <?php | |
| $args = array( | |
| 'meta_query' => array( | |
| array( | |
| 'key' => 'featured_post', | |
| 'value' => '1', | |
| 'compare' => '=', | |
| ), | |
| ), | |
| 'no_found_rows' => true, | |
| 'fields' => 'ids', | |
| ); | |
| $q = new WP_Query( $args ); |
The key in the meta_query parameter is the name of your custom field. The value is what you’re matching against. The compare field sets the comparison operator (equals, greater than, LIKE, etc.), which we’ll cover in detail below.
WP_Query returns an array of posts you can loop through with a standard WordPress loop. This guide focuses on the meta_query parameters rather than how to display the results.
Query Posts By Multiple Meta Values
If you want to check a meta key value against multiple values it’s best to use the ‘IN’ operator. This is done by using an array for ‘value’ in your meta query.
| <?php | |
| $args = array( | |
| 'meta_query' => array( | |
| array( | |
| 'key' => 'city_name', | |
| 'value' => array('New York City', 'London', 'San Francisco'), | |
| 'compare' => 'IN', | |
| ), | |
| ), | |
| ); | |
| $q = new WP_Query( $args ); |
Query Posts By Multiple Meta Keys
To filter by multiple custom fields at once, add multiple arrays inside meta_query and set a relation parameter.
| <?php | |
| $args = array( | |
| 'meta_query' => array( | |
| 'relation' => 'AND', | |
| array( | |
| 'key' => 'city_name', | |
| 'value' => array('New York City', 'London', 'San Francisco'), | |
| 'compare' => 'IN', | |
| ), | |
| array( | |
| 'key' => 'featured_post', | |
| 'value' => '1', | |
| 'compare' => '=', | |
| ), | |
| ), | |
| ); | |
| $q = new WP_Query( $args ); |
The relation field can be set to AND or OR. Use AND when all conditions must match, and OR when any one condition is enough.
Nested Meta Queries
You can nest meta_query arrays to create complex logic. For example, “get posts where color is red AND (size is large OR size is medium)”:
$args = array(
'post_type' => 'product',
'meta_query' => array(
'relation' => 'AND',
array(
'key' => 'color',
'value' => 'red',
),
array(
'relation' => 'OR',
array(
'key' => 'size',
'value' => 'large',
),
array(
'key' => 'size',
'value' => 'medium',
),
),
),
);
$query = new WP_Query( $args );
The outer relation is AND, so both conditions must be true. The inner array uses OR, so either size value will match. You can nest as many levels as needed.
Query Posts Between Dates
You can filter posts by date values stored in custom fields. Here’s how to query posts between two dates.
This function assumes your date is formatted as YYYY-MM-DD in structure. If you’re using Advanced Custom Fields you’ll want to ensure your return date format is set to return in this format.
| <?php | |
| $args = array( | |
| 'meta_query' => array( | |
| array( | |
| 'key' => 'my_date_field', | |
| 'value' => array('2022-01-01', '2022-12-31'), | |
| 'type' => 'DATE', | |
| 'compare' => 'BETWEEN', | |
| ), | |
| ), | |
| ); | |
| $q = new WP_Query( $args ); |
If you’re using a Unix timestamp it’s even easier to query posts between dates. This code snippet will convert dates to Unix timestamps and compare between them.
| <?php | |
| $meta_query_args = array( | |
| 'meta_query' => array( | |
| array( | |
| 'key' => 'my_date_field', | |
| 'value' => array( strtotime('2022-01-01'), strtotime('2022-12-31') ), | |
| 'type' => 'numeric', | |
| 'compare' => 'BETWEEN' | |
| ) | |
| ) | |
| ); | |
| $meta_query = new WP_Query( $meta_query_args ); |
Of course you can also use the greater than or less than operator in the above queries to find posts before/after a date.
The type Parameter
By default, WordPress compares meta values as strings. This causes problems when you need numeric or date comparisons. For example, comparing the string “9” with “10” would put 9 after 10 alphabetically. The type parameter tells WordPress to cast values before comparing.
Available type values:
- NUMERIC: Cast as an integer (most common for number fields)
- CHAR: String comparison (default)
- DATE: Date comparison (expects YYYY-MM-DD format)
- DATETIME: Date and time comparison
- DECIMAL: Decimal number comparison
- SIGNED: Signed integer (allows negative numbers)
- UNSIGNED: Unsigned integer (positive numbers only)
- TIME: Time comparison
- BINARY: Binary comparison (case-sensitive)
Here’s an example using NUMERIC to find posts with a price greater than 50:
$args = array(
'post_type' => 'product',
'meta_query' => array(
array(
'key' => 'price',
'value' => 50,
'compare' => '>',
'type' => 'NUMERIC',
),
),
);
$query = new WP_Query( $args );
Without 'type' => 'NUMERIC', WordPress would compare “50” as a string and return incorrect results for values like “9” or “100”.
Check If a Meta Key EXISTS or NOT EXISTS
Sometimes you need to find posts where a custom field exists at all, regardless of its value. The EXISTS and NOT EXISTS operators handle this. You don’t need to set a value when using these operators.
// Get posts that HAVE a 'featured_image_url' custom field
$args = array(
'post_type' => 'post',
'meta_query' => array(
array(
'key' => 'featured_image_url',
'compare' => 'EXISTS',
),
),
);
$query = new WP_Query( $args );
// Get posts that do NOT have a 'hide_from_homepage' field
$args = array(
'post_type' => 'post',
'meta_query' => array(
array(
'key' => 'hide_from_homepage',
'compare' => 'NOT EXISTS',
),
),
);
$query = new WP_Query( $args );
This is useful for filtering out posts that haven’t been assigned a specific custom field yet, or for finding posts where a field was intentionally left empty.
Search Meta Values with LIKE
The LIKE operator finds posts where a custom field contains a substring. WordPress wraps the value with % wildcards automatically, so searching for “john” matches “John”, “Johnson”, and “john_doe”.
$args = array(
'post_type' => 'post',
'meta_query' => array(
array(
'key' => 'author_name',
'value' => 'john',
'compare' => 'LIKE',
),
),
);
$query = new WP_Query( $args );
Use NOT LIKE to exclude posts that contain the substring. Both operators are case-insensitive by default.
Meta Query Comparison Operators
Here is every comparison operator available in meta_query and what it does:
| Operator | Description | Value Type |
|---|---|---|
= | Equal to (default) | String |
!= | Not equal to | String |
> | Greater than | String/Numeric |
>= | Greater than or equal to | String/Numeric |
< | Less than | String/Numeric |
<= | Less than or equal to | String/Numeric |
LIKE | Contains substring (case-insensitive) | String |
NOT LIKE | Does not contain substring | String |
IN | Matches any value in an array | Array |
NOT IN | Does not match any value in an array | Array |
BETWEEN | Value falls between two values | Array (2 items) |
NOT BETWEEN | Value falls outside two values | Array (2 items) |
EXISTS | Meta key exists (no value needed) | None |
NOT EXISTS | Meta key does not exist (no value needed) | None |
REGEXP | Matches a regular expression pattern | String (regex) |
NOT REGEXP | Does not match a regular expression | String (regex) |
RLIKE | Alias for REGEXP | String (regex) |
When using the simple meta_key/meta_value shorthand instead of the meta_query array, use meta_compare to set the operator.
How To Sort Posts By Meta Fields
WordPress lets you sort query results by custom field values using the orderby parameter in WP_Query.
Set orderby to meta_value and add a meta_key with your field name. This sorts results alphabetically by that field’s value.
| <?php | |
| $meta_query_args = array( | |
| 'post_type' => 'page', | |
| 'order' => 'ASC', | |
| 'meta_key' => 'city_name', | |
| 'orderby' => 'meta_value' | |
| ); | |
| $meta_query = new WP_Query( $meta_query_args ); |
If your field stores numbers, use meta_value_num instead so WordPress sorts numerically rather than alphabetically:
$args = array(
'post_type' => 'product',
'meta_key' => 'price',
'orderby' => 'meta_value_num',
'order' => 'ASC',
);
$query = new WP_Query( $args );
Sort By Multiple Meta Fields
To sort by more than one custom field, give each meta_query clause a name (an alias) and reference those names in orderby:
$args = array(
'post_type' => 'product',
'meta_query' => array(
'price_clause' => array(
'key' => 'price',
'type' => 'NUMERIC',
'compare' => 'EXISTS',
),
'rating_clause' => array(
'key' => 'rating',
'type' => 'NUMERIC',
'compare' => 'EXISTS',
),
),
'orderby' => array(
'price_clause' => 'ASC',
'rating_clause' => 'DESC',
),
);
$query = new WP_Query( $args );
This sorts products by price (lowest first), then by rating (highest first) for products at the same price.
WordPress converts your meta_query parameters into optimized SQL behind the scenes, so always use WP_Query rather than writing raw MySQL queries. For the full list of WP_Query parameters beyond meta_query, see the WP_Meta_Query documentation on wordpress.org. For more WordPress development tips, check out our useful code snippets or learn how to insert posts programmatically.



5 Responses
Good job
Clear post to understand!
Thank you Andy
So happy it was helpful!
Hi, is it possible to have multiple meta_queries with AND / OR?
So I have four variables stored in custom fields, ‘user-1’, ‘user-2’ (both to be checked against current user), ‘date’ (checked against current date), ‘status’. (action/filing) and what i want to achieve in the returned posts is the following;
Result should be as follows;
Query 1 is == ‘user-1’ OR ‘user-2’ can be current user
Query 2 is == AND ‘date’ is earlier than today AND ‘status’ is (action)
I can get it to work using just one ‘user’ custom field, because then it is just three AND relations, but not to have both queried as it then feels like i have to have two meta_queries, which I don’t know is possible? I have been using Oxygen repeater with query builder to make my query rather than coding it directly. Any thoughts on the matter would be appreciated.
hi
this code don’t work!