Custom Loop/Query Based on Custom Fields

Avatar of Chris Coyier
Chris Coyier on (Updated on )
Last updated by Jason Witt.

If you design or develop WordPress themes or plugins, there’s a good chance that someday you’ll need to make a query for custom meta fields. These are those completely custom key/value pairs that you can attach to any post, page, or custom post type. WordPress has a basic UI for them by default, or you can use something like Advanced Custom Fields to get fancy with them. But under the hood ACF uses regular ol’ custom fields.

This very snippet page you are looking at right now was written in 1999. At that time, in order to query for posts with particular custom fields, you would need to use the `$wpdb` global variable. That can be used for creating MySQL queries that the WordPress WP_Query() class doesn’t support. Fortunately today, WordPress does have arguments that support queries for custom meta fields.

Here, we’ll cover the different ways you can request and loop over posts with particular custom fields (and their values). You’ll be able to use this information whether you use the WP_Query class, query_posts(), or get_posts(). Since query_posts() and get_posts() are wrappers for the WP_Query class. They all accept the same arguments.

The Query Arguments

Here is a basic example of a WordPress query taken from the WordPress Codex.

<?php
// The Query
$the_query = new WP_Query( $args );

// The Loop
if ( $the_query->have_posts() ) {
  echo '<ul>';
  while ( $the_query->have_posts() ) {
    $the_query->the_post();
    echo '<li>' . get_the_title() . '</li>';
  }
  echo '</ul>';
} else {
  // no posts found
}
/* Restore original Post Data */
wp_reset_postdata();

The $args is the important bit there. We’ll be passing different arguments to make this work how we want.

When querying for custom meta, there are two “groups” of arguments that you can use. One group is for a simple custom meta field query and the other group of for more complex custom meta fields queries. Let’s start with the simple group.

meta_key

The meta_key argument will query any post that has the custom field meta ID saved to the database, whether or not there is a value saved for the field. The meta_key is the ID that you give to your meta fields. Like this:

This example will query any post that has the custom meta field with the ID of “field1”.

$args = array( 'meta_key' => 'field1' );

meta_value

The meta_value argument queries post that have the value you define. The meta_value argument is used for string values. This example will query any posts with a custom meta field that has the value “data1”.

$args = array( 'meta_value' => 'data1' );

You can also combine the two. This example will only query posts that have the custom meta field with the ID of “field1” that has the value of “data1”.

$args = array(
  'meta_key'   => 'field1', 
  'meta_value' => 'data1'
);

meta_value_num

The meta_value_num argument is similar to the `meta_value` argument. Where the meta_value argument is ment for string values the meta_value_num is meant for numerical values.

This example shows how to query the “field1” custom meta field if it has a value of “10”.

$args = array(
  'meta_key'       => 'field1', 
  'meta_value_num' => '10',
);

meta_compare

The meta_compare argument does exactly what it sounds like. It’ll allow you to use comparators with the `meta_value` and `meta_value_num` arguments. The comparators you can use are ‘=’, ‘!=’, ‘>’, ‘>=’, ‘<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'NOT EXISTS', 'REGEXP', 'NOT REGEXP' or 'RLIKE'. Here's an example that shows how to query any posts that don't have the value of "data1".

$args = array(
  'meta_key'    => 'field1', 
  'meta_value'  => 'data1',
  'meta_compare' => '!=',
);

More Complex Queries

meta_query

The main argument you’ll use for complex queries is meta_query. This argument on it’s own doesn’t do anything. It just tells WordPress that you want to make a query for custom meta fields. You’ll add additional arguments within meta_query that will be used to define the query.

key, value, and compare

The arguments key, value work exactly the same way as meta-key, meta-value as described above. The complex compare is similar to the simple compare above, but it take a different list of comparators. The complex compare uses ‘=’, ‘!=’, ‘>’, ‘>=’, ‘<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'EXISTS', or 'NOT EXISTS'. value can be an array, but only when compare is using ‘IN’, ‘NOT IN’, ‘BETWEEN’, or ‘NOT BETWEEN’.

If you use ‘EXISTS’, or ‘NOT EXISTS’ with compare, you do not need to specify a value argument.

Here is an example that will query posts if it has “field1” with the value “data1”, and “field2” with the value that is not “data2”.

$args = array(
  'meta_query' => array(
    array(
      'key'   => 'field1',
      'value' => 'data1'
    ),
    array(
      'key'     => 'field2',
      'value'   => 'data2',
      'compare' => '!=',
    )
  ) 

);

relation

The relation is used when you want to query custom meta data using a logical relationship. You can use AND or OR. For example you’ll use AND to compare if data1 and data2 meet the criteria, and you use OR if data1 or data2 meet the criteria.

This argument is stand-alone. Meaning it doesn’t appear in a individual custom meta field parameters. Let’s look at an example. This example will only query posts that have “field1” with the value of “data1”, and “field2” with the value of “data2”.

$args = array(
  'meta_query' => array(
    'relation' => 'AND'
    array(
      'key'   => 'field1', 
      'value' => 'data1',
    ),
    array(
      'key'   => 'field2', 
      'value' => 'data2',
    ),
  )
);

If you changed relation to “OR”. Then it would query any posts if “field1” has the value of “data1”, or if “field2” has the value of “data2”.

type

The type argument allows you to choose the type of data to query. You can use ‘NUMERIC’, ‘BINARY’, ‘CHAR’, ‘DATE’, ‘DATETIME’, ‘DECIMAL’, ‘SIGNED’, ‘TIME’, or ‘UNSIGNED’.

The “DATE” type can be used with the compare “BETWEEN” only if the date format is “YYYYMMDD”.

This example will query any post where the value of “field1” is numeric.

$args = array(
  'meta_query' => array(
    array(
      'key'   => 'field1', 
      'value' => 'data1',
      'type' => 'NUMERIC'
    )
  )
);

Real World Example

So far, I’ve only given examples with arbitrary data and fields. Now, I’d like to show you a real world example of querying custom meta fields.

The Scenario

You’ve created an events custom post type. The events post type has a date custom field with the ID of event_date. You want to create a query that will show any events that will be starting on the current date through the next 30 days.

We’re going to use the meta_query argument as we want to use the type argument to define the “event_date” field as “DATE” data type.

This is the query:

$args = array(
  'post_type'      => 'post',
  'posts_per_page' => -1,
  'post_status'    => 'publish',
  'meta_query'     => array(
    array(
      'key'     => 'event_date',
      'value'   => array( date( 'Ymd', strtotime( '-1 day' ) ), date( 'Ymd', strtotime( '+31 days' ) ) ),
      'compare' => 'BETWEEN',
      'type'    => 'DATE'
    ) 
  )
);
$event_query = new WP_Query( $args );

The value is an array of the current date – 1 day and 31 days from the current date. Since we’re using the comparator “BETWEEN” only the posts between the value array will be queried, so we want to offset them by one day.

With this query you’ll display any event occurring in the next 30 days.

Conclusion

The WP_Query class is a very flexible class that will allow you to create a multitude of custom queries. If you want to learn more about the different arguments you can use for queries I recommend looking through the WP_Query codex page.