WordPress Meta_Query: Everything You Need to Know

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:

OperatorDescriptionValue Type
=Equal to (default)String
!=Not equal toString
>Greater thanString/Numeric
>=Greater than or equal toString/Numeric
<Less thanString/Numeric
<=Less than or equal toString/Numeric
LIKEContains substring (case-insensitive)String
NOT LIKEDoes not contain substringString
INMatches any value in an arrayArray
NOT INDoes not match any value in an arrayArray
BETWEENValue falls between two valuesArray (2 items)
NOT BETWEENValue falls outside two valuesArray (2 items)
EXISTSMeta key exists (no value needed)None
NOT EXISTSMeta key does not exist (no value needed)None
REGEXPMatches a regular expression patternString (regex)
NOT REGEXPDoes not match a regular expressionString (regex)
RLIKEAlias for REGEXPString (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.

Picture of Andy Feliciotti

Andy Feliciotti

Andy has been a full time WordPress developer for over 10 years. Through his years of experience has built 100s of sites and learned plenty of tricks along the way. Found this article helpful? Buy Me A Coffee

5 Responses

  1. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *

WordPress Tips Monthly
Get the latest from SmartWP to your inbox.