Treehouse: Grow your CSS skills. Land your dream job.

Last updated on:

Custom Loop/Query Based on Custom Fields

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.

Comments

  1. Permalink to comment#

    This is great! What if you want to limit the loop to 5 posts/pages?

  2. Permalink to comment#

    Okay, that was easy. Just added LIMIT 4 to the query details…

  3. hmm..is it support wordpress 2.9???

  4. Permalink to comment#

    Great stuff, using it in several places in one of my wp templates.

    In addition to this, is there a way to query mulitiple custom fields and print the “merged” results?

  5. Isn’t this the same result you’d obtain using wp_query?

    Something like this:

    http://wordpress.pastebin.com/uJDdsiCw

    I used this reference:

    http://codex.wordpress.org/Template_Tags/query_posts#Custom_Field_Parameters

    but haven’t tried it yet.

    • It actually can. I just used it that way.
      I couldnt implement the code in the article whether for noobness or didnt understand where to put it.

      I got your code and integrated it with a simpler query;
      $loop = new WP_Query( $args );
      if($loop->have_posts()): while ( $loop->have_posts() ) : $loop->the_post();

    • Here’s an easier way to accomplish this task. http://pastie.org/2464333 It works like a charm every time!

    • sabbir
      Permalink to comment#

      The flexibility of using SQL query is more than custom post queries. For example if you want to search through a range or something like that.

  6. I’ve been trying to figure out, how to show posts from a specific category, if the page template being viewed has a custom field value with the same name.

    ie. a custom field value is present, and posts in a category with the same name as that custom field value are shown.

  7. carol
    Permalink to comment#

    I am trying to use this to output child pages of a certain parent page that have a particular custom field. Problem is it spits out more than one of the same child page depending on how many values have been declared for the custom field. That is if the child page has two different costume field VALUES for “soundfile” it will output that childpage twice. How can I limit that. Here is the link to the page I am working on:

    http://www.oliviablock.net/listen/

    Thanks in advance

  8. carol
    Permalink to comment#

    OK, by some freakin miricle I was able to figure out the solution to the above comment. Instead of OBJECT I used OBJECT_K which results output as an associative array of row objects, using first column’s values as keys so that duplicates where discarded.

    Thanks,
    carol

  9. Permalink to comment#

    Thanks for this Chris!!! You just saved my life with this one! :)

  10. Permalink to comment#

    Fantastic! This worked like a charm for me, thanks so much!

  11. Permalink to comment#

    Very nice Chris. This gives me the control I was looking for for out putting post content on my home page. Cheers mate.

  12. Fannar
    Permalink to comment#

    I wanted to thank you for this super useful post. It basically saved my life. I have spent so much time trying to do something similar with the wordpress built in queries and not been able to.

    Thank you !

  13. PG
    Permalink to comment#

    Really good post Chris, I almost thought it would solve a problem I’m having, which I’ve looked everywhere for info on but couldn’t find a solution. I’m hoping someone here will have some idea of how to do this or if it’s even possible.

    Below is part of the code from this post, my custom field is called ‘views’ and by using the wp-postviews plugin, that custom field is given the number of how many people have viewed that post. Is there any way to get the last line, instead of ordering the posts by date, to order them by the custom field view number values, in descending order? I’ve all but given up on trying, so if anyone knows I’d be very grateful!

    $querydetails = ”
    SELECT wposts.*
    FROM $wpdb->posts wposts, $wpdb->postmeta wpostmeta
    WHERE wposts.ID = wpostmeta.post_id
    AND wpostmeta.meta_key = ‘views’
    AND wposts.post_status = ‘publish’
    AND wposts.post_type = ‘post’
    ORDER BY wposts.post_date DESC

  14. PG
    Permalink to comment#

    Just to update, someone else was kind enough to update the current code I had (not the one above) to order posts by view count. Just thought I’d share it below incase anybody is looking to do the same thing.

         $posts_per_page,
           'paged' => $paged,
           'more' => $more = 0,
           'meta_key' => 'views',
           'orderby' => 'meta_value_num',
           'order' => 'DESC',
        ); ?>
    
  15. This is a great post!
    With this script, based on custom fields, WordPress is more flexible.
    Thank you! You’ve saved my work. :)

  16. Permalink to comment#

    I know this is a year old post, but I have been searching for a solution to my problem and I have yet to find a solution.

    Code wise, I am using exactly what you have posted. However, the query only retrieves one result.

    Any ideas what might cause this?

    Thanks in advance!

  17. kamal

    i am unable to change the loop query in the
    code

    how can i modify
    it by custom fields ..

    and query

    $paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
                          $loop = new WP_Query();
                          $loop->query( array( 'post_type' => 'page' , 'meta_key' => 'portfolio_image',  'showposts' => 5, 'orderby' => date, 'order' =>  'DESC' ,'paged'=> $paged));
    

    please any one help me ….

  18. Actually i have ti implement the where and like conditions in the query.

    for the searching by a particular values

  19. 4nt0n
    Permalink to comment#

    Well indeed an old post but I would to implement the same query but with multiple conditions.somthing like this:

    $querystr = "
            SELECT wposts.*
            FROM $wpdb->posts wposts, $wpdb->postmeta wpostmeta
            WHERE wposts.ID = wpostmeta.post_id
            AND wpostmeta.meta_key = 'parent_cat'
            AND wpostmeta.meta_value = 'methods'
            AND wpostmeta.meta_key = 'main_item'
            AND wpostmeta.meta_value = 'true'
            AND wposts.post_status = 'publish'
            AND wposts.post_type = 'post'
            ORDER BY wposts.post_date DESC
         ";
    
  20. Hi Chris

    I was just about to use this code when I spotted what I think to be a tiny error.

    On the line:

    <div <?php post_class(); ?>> id="post-<?php the_ID(); ?>">
    

    I think there is one too many ‘>’ after the first PHP segment. The div is being ended too soon.

  21. Abhijit Aitwade
    Permalink to comment#

    Great post! Solved custom fields sql problem.

    Thanks!

  22. Miles
    Permalink to comment#

    THANKS.. GREATT TUT REALLY HELPED.

  23. Permalink to comment#

    Works great, thanks so much! :-)

  24. Works great :) thanks for sharing it

  25. brilliant… (so is a lot of the design on this site, i might add!)
    one question… is there a “wildcard” option for wpostmeta.meta_value ?
    basically if there’s NO data in the meta_key i’ve selected, i want to hide the loop, but as long as there’s SOMETHING in it, i’d like it to appear…
    there are too many variables (and more added on occasion) to make it viable to have to hard code an array…

    i’ve tried $key and that works as long as there is a value, but when there is no value it actually displays the query output (with no data) twice…

  26. Swami.M

    I’m trying to use this to create a custom calendar, but because I’m very new at this, I am kinda lost. I know that I need to use the above code to pull out the meta data for the date. But the posting of the title and permalink within the specific date of the calendar is beyond me. I know that I can pull the title and permalink and display as above, but how can I get it to display in the correct date box for the calendar? Anyone got any ideas?

    PS: Like I said above, I’m very new at this, so I’m trying to figure things out as I go. Please excuse my ignorance.

  27. why not using this snippet?

    $the_query = new WP_Query(array( 'meta_key' => 'open', 'meta_value' => 'yes' ));
    
  28. Am going to have to use this for a really weird archive layout I’ve been asked to build; does anyone know how I’d go about using WordPress’ inbuilt pagination with these results? I know I can use OFFSET and LIMIT in the SQL nad do it manually, but I’d ideally like to hook into the default WordPress implementation if possible.

  29. Permalink to comment#

    Good stuff! Been using it for a bunch of queries! Thanks.

    • burki
      Permalink to comment#

      hi all.
      i need to check two meta keys and meta values. if any of the meta key and meta value is find then get the post and the related meta data.
      like
      where ( wpostmeta.meta_key = ‘any’
      AND wpostmeta.meta_value = ‘any’)
      Or

      ( wpostmeta.meta_key = ‘another’
      AND wpostmeta.meta_value = ‘another’),

      please help, and sorry for weak english.

  30. burki
    Permalink to comment#

    hi all.
    i need to check two meta keys and meta values. if any of the meta key and meta value is find then get the post and the related meta data.
    like

    where ( wpostmeta.meta_key = ‘any’
    AND wpostmeta.meta_value = ‘any’)
    

    Or

    ( wpostmeta.meta_key = ‘another’
    AND wpostmeta.meta_value = ‘another’),
    

    please help, and sorry for weak english.

  31. Sasha
    Permalink to comment#

    For those who don’t want to mess with the code, there is a very easy solution though the Advanced Post Types Order plugin more details at http://www.nsp-code.com/how-to-order-wordpress-posts-using-a-custom-field-value/<a/&gt;

  32. Jack
    Permalink to comment#

    Question about this. I understand that when using wp_query the metadata and other goodies are cached. Do you lose those speed benefits with this query?

  33. Permalink to comment#

    Now i am able to write custom queries to show post meta data which i wanted in my custom loop. You are Awesome :)

  34. Some good tips here but I am trying to get the custom field to be equal to the page title and that way I can have many pages using the same loop.

  35. Thank you for sharing of this snippet! You made my day!

  36. Julian
    Permalink to comment#

    Thanks , works perfect ! :)

  37. Amazingly, the MIN() & GROUP BY works and I was able to get the results I wanted via the “SQL” below, but am having some trouble.

    SELECT p.*, MIN(pm.meta_value) as date
    FROM $wpdb-&gt;posts p, $wpdb-&gt;postmeta pm
    WHERE p.ID = pm.post_id
    AND p.post_type = 'performances'
    AND p.post_status = 'publish'
    AND pm.meta_key in ('date_one','date_two','date_three','date_four')
    AND pm.meta_value &gt; NOW()
    GROUP BY p.ID
    

    Now I want to access that single MIN(pm.meta_value) as date per p.ID. That’s a vital piece of information and I don’t know how to get that returned in the resulting loop.

  38. Permalink to comment#

    Hi, good stuff. Interesting article but I think for the sake of this article you must use the GROUP BY clause not to get duplicate rows.
    It would be also useful to add pagination stuff due to the way you query using SQL and not WP_Query.

    I think this should work better:
    SELECT wposts.*
    FROM posts wposts,postmeta wpostmeta
    WHERE wposts.ID = wpostmeta.post_id
    AND wposts.post_status = ‘publish’
    AND wposts.post_type = ‘post’
    GROUP BY wposts.ID
    ORDER BY wposts.post_date DESC;

    I’ve used this on http://www.glocalmart.it where I just try to fit the search form needs by querying w/o using WP_Query.
    Thank you.

  39. This should REALLY redirect to post v.3.2 standards, do you have a tutorial on meta queries?

    http://codex.wordpress.org/Class_Reference/WP_Meta_Query

    This whole thing should be done through the query system, not a custom select statement.

Leave a Comment

Posting Code

We highly encourage you to post problematic HTML/CSS/JavaScript over on CodePen and include the link in your post. It's much easier to see, understand, and help with when you do that.

Markdown is supported, so you can write inline code like `<div>this</div>` or multiline blocks of code in in triple backtick fences like this:

```
<script>
  function example() {
    element.innerHTML = "<div>code</div>";
  }
</script>
```