Using WP_Query In WordPress
If you’ve been around WordPress for a while, you’ll know how difficult it used to be to create lists of posts based on complex criteria while also conforming to WordPress’ standards. Over the course of a few years, the platform has come a long way. By using the power of the WP_Query class, we can lists posts in any way we want.
What Is WP_Query?
The WP_Query class is one of the most important parts of the WordPress codebase. Among other things, it determines the query you need on any given page and pulls posts accordingly. It also saves a lot of information about the requests it makes, which helps a great deal when optimizing pages (and troubleshooting them).
The other role of WP_Query is to enable us to perform complex database queries in a safe, simple and modular manner.
Safety
Throughout our interactions with this object, we are supplying parameters and using functions to reach our goal. The object’s internals take care of many annoyances, such as protecting against SQL injection attacks and making sure that proper data types are used.
Simplicity
The object abstracts much of the query complexity away so that we don’t have to muck about with the specifics of our database. Because we are using an array to supply our criteria, everything is more self-explanatory. No manual database joins or nested queries are needed — just create an arguments array and instantiate the class!
Modularity
My favorite of all is modularity. When making raw queries, it is hard to manage those frequently used bits because they are just fragments of SQL code. WP_Query does away with this by using an associative array as an argument. A plethora of goodness ensues: you can merge arguments from different places, run array functions to your heart’s content and manipulate it in ingenious ways.
Getting Started
The “Regular” WordPress Loop
Let’s look at a regular loop first, and then create the same loop using WP_Query. Let’s assume that we’re coding in the category.php file.
<?php
if(have_posts()) :
while(have_posts()) :
the_post();
?>
<h1><?php the_title() ?></h1>
<div class='post-content'><?php the_content() ?></div>
<?php
endwhile;
else :
?>
Oops, there are no posts.
<?php
endif;
?>
The Same Loop Using WP_Query
<?php
$args = array('cat' => 4);
$category_posts = new WP_Query($args);
if($category_posts->have_posts()) :
while($category_posts->have_posts()) :
$category_posts->the_post();
?>
<h1><?php the_title() ?></h1>
<div class='post-content'><?php the_content() ?></div>
<?php
endwhile;
else:
?>
Oops, there are no posts.
<?php
endif;
?>
As you can see, there is not much difference at all! Let’s break it down:
- Constructing a query
On a category page, WordPress already knows that you want to list posts from that category. Because we are constructing a query from scratch usingWP_Query, we need to specify this ourselves. We’ll delve a bit deeper into this in a bit. - Instantiating the class and querying for posts
By instantiating a class with the constructed argument array,WP_Querywill try to pull the posts specified and a load of other details. - Creating a loop
You can use all of the usual functions; just be sure to use them as the methods of your object:- Instead of
have_posts(), use$category_posts->have_posts(). - Instead of
the_post(), use$category_posts->the_post().
- Instead of
- Resume business as usual
Once you’ve done the above, you can use all of the template tags you’ve come to know and love.
If you look at this in detail, you will find that the global $post object is also available. This means that if you use a custom loop like this within another loop, things can go wrong. Be sure to store the original value of the $post object and restore it after the loop.
<?php
$temp_post = $post; // Storing the object temporarily
$my_query = new WP_Query();
while($my_query->have_posts()) {
// Loop in here
}
$post = $temp_post; // Restore the value of $post to the original
?>
Digging Deeper
The Power of a Good Argument
The ease with which we can create loops is obvious, but what about actually querying for posts? Let me show you a common technique I use when creating sliders for commercial themes.
In many cases, users of your theme will want a great-looking slider, but they might be a bit lazy in creating content. Many users will also want to show future content. Let’s query for upcoming (i.e. unpublished) posts that have an attached featured image.
<?php
$args = array(
'post_status' => 'future',
'meta_query' => array(
array(
'key' => '_thumbnail_id',
'value' => '',
'compare' => '!='
)
)
);
$slider_posts = new WP_Query($args);
?>
<?php if($slider_posts->have_posts()) : ?>
<div class='slider'>
<?php while($slider_posts->have_posts()) : $slider_posts->the_post() ?>
<div class='slide'>
<?php the_post_thumbnail() ?>
</div>
<?php endwhile ?>
</div>
<?php endif ?>
Short, sweet and utterly understandable — just beautiful. And we’ve just scraped the surface.
Know Your Defaults
You may have noticed that I didn’t specify a number of things in my queries. What about how many posts to list? What about the post’s status in the first query we made?
Default values are supplied for many of the most common arguments. Here are a few that you don’t have to specify, unless you want to change them:
posts_per_page
Defaults to the value specified in the reading settings for the number of posts to list.post_type
Defaults topost.post_status
Defaults topublish.
You can find the complete list of parameters in the Codex, of course!
Arrays Are Awesome
In many cases, you will want to specify a number of values that an argument can take. Where it would seem logical, WP_Query usually allows you to use arrays to make your life easier. Here are a few examples:
- You can use an array for
post_statusto pull posts from a number of different statuses. Note that you can use the stringanyto get posts from all statuses. - If you use custom post types, you’ll be happy to hear that you can use an array for the value of the
post_typeparameter as well. - For the taxonomy type parameters
category__in,tag__inand so on, you can use an array to indicate a multitude of values.
Handling Taxonomies
WP_Query is nice enough to offer a simple way to make advanced taxonomy queries as well. This is especially useful for websites with complex set-ups and for commercial themes with large feature sets. The mechanism used is called tax_query. Let’s look at an example.
Say you have a website all about movies. You store movies in a custom “movie” post type; you have a custom taxonomy for genre, a custom taxonomy for actors, and you use the regular ol’ category to indicate how good a movie is. Let’s find all “Action” movies starring “Bruce Willis” that aren’t “Bad”:
<?php
$args = array(
'post_type' => 'movie',
'tax_query' => array(
'relation' => 'AND',
array(
'taxonomy' => 'category',
'field' => 'slug',
'terms' => array('bad')
'operator' => 'NOT IN'
),
array(
'taxonomy' => 'genre',
'field' => 'slug',
'terms' => array('action')
),
array(
'taxonomy' => 'actor',
'field' => 'slug',
'terms' => array('Bruce Willis'),
)
)
);
?>
While this hardcoded example would be useful only to people who love Die Hard, it’s not hard to see how an advanced filter can be built that lets users filter through your content in any which way they want.
Learn more about all of the awesome things you can do with the taxonomy parameters in the Codex.
Having Fun With Meta Data
You’ve already seen that WP_Query is great at handling meta data — we used a meta_query in the second example to build a slider from posts that have featured images. Just as with the taxonomy queries, a lot of flexibility is built in here.
We’re currently building a WordPress theme to be used to create a Web page for apartment(s) for rent. We store apartments as a custom post type and use meta data to store the details. With a meta query, we can easily pull in all apartments that can house four or more people, that have a balcony and that are non-smoking.
<?php
$args = array(
'post_type' => 'apartment',
'meta_query' => array(
'relation' => 'AND',
array(
'key' => 'persons',
'value' => '4',
'compare' => '>=',
'type' => 'NUMERIC'
),
array(
'key' => 'balcony',
'value' => '1',
'type' => 'BINARY',
'compare' => '='
),
array(
'key' => 'smoking',
'value' => '0',
'type' => 'BINARY',
'compare' => '='
)
)
);
?>
Again, this is very modular, very understandable. To learn more about the parameters you can use, just visit the “Custom Field Parameters” section in the WP_Query documentation.
Methods And Properties
Once you’ve made a query, you can coax a lot of information out of your object. You can find a full list of “Methods and Properties” in the Codex. Here are the ones I tend to use most:
$query
Shows you the query string passed to the$wp_queryobject. This is quite helpful for troubleshooting in some advanced cases.$query_vars
Shows an associative array of the arguments you’ve passed. If you do plenty of mixing and matching before passing arguments, this tool could be helpful indeed to check that all is well.$posts
Holds the requested posts from the database. I rarely use this property, but it’s good to know that this is where your items come from.$found_posts
A handy little thing that shows the total number of found items (without the limit imposed by theposts_per_pageargument).
With Great Power Comes Great Responsibility
While WP_Query gives you plenty to play around with, it does have its drawbacks. Many people (my past self included) go nuts when they realize how easy it is to bang out queries all over the place.
Keep in mind that more queries mean more server load. I’ve found that on hosted systems, complex queries can be especially naughty because they eat at your RAM, which is probably your scarcest resource.
Make sure to check out what the default query holds on each page. What’s the sense in creating a new query for the latest posts on the front page if it’s there by default? Use what you can more than once, cache results, and so on.
(al)






Raherian
January 15th, 2013 3:48 amHi,
If you call the_post() on on a custom query, be sure to call wp_reset_postdata() after the endwhile for not editing the main query.
Cameron Baney
January 15th, 2013 8:58 amI second this, especially if you want to do more than one query per page/template.
Konstantin Kovshenin
January 15th, 2013 10:56 amWhat Raherian said, use
wp_reset_postdatato restore the post global from the original query. Also, “Bruce Willis” is probably not a valid term slug, though “bruce-willis” is. Great post, keep it up!Daniel Pataki
January 15th, 2013 11:29 amLol, good point! Also, apologies for not mentioning wp_reset_postdata properly, good point!
ben
January 15th, 2013 4:16 amMore informations for Default Loop, Loop with query_posts(), Loop with WP_Query(), Loop with get_posts() on http://digwp.com/2011/05/loops/ :)
Konstantin Kovshenin
January 15th, 2013 10:58 amYeah, except that you shouldn’t use
query_postsbecause it’s evil.Daniel Pataki
January 15th, 2013 11:29 amIt has its place. It is ok for a low volume website and a quick fix, but it is definitely duplicating queries. If someone is interested in doing it a bit better, pre_get_posts is the hook to look for I think.
Tom Hermans
January 15th, 2013 4:21 amIf you want to do it really good, look at this presentation by Andrew Nacin, one of WordPress’ core developers.
http://www.slideshare.net/andrewnacin/you-dont-know-query-wordcamp-netherlands-2012
Konstantin Kovshenin
January 15th, 2013 11:01 amAnd the corresponding video from Portland: http://wordpress.tv/2012/06/15/andrew-nacin-wp_query/ If you use
WP_Query,get_postsorquery_postsin your projects, you should watch this ten times, and stop usingquery_posts:)Paul
January 15th, 2013 4:37 amGreat explanation and examples, except I’d always use taxonomies when many posts would have common values like smoking/non-smoking and custom fields when values will be unique to the post.
Jack Neary
January 15th, 2013 7:03 amThis seems like a poor example to me, as by default WordPress’s taxonomies can’t be register to be “exclusive” you would be able to select both smoking & non-smoking for a single post, and in this example the value would seem to suit a Boolean’s behavior. You can of course either deregister the standard taxonomies meta box and register a replacement one rendering radio inputs instead of checkboxs, or just replace the content of the existing meta box via JavaScript. But in this example I can’t see how it would be justified. You’re not going to be adding any additional terms to a “smoking” taxonomy. It’s either going to be a true (smoking) or a false (non-smoking), Custom fields are definitely a more suitable solution to this scenario.
Konstantin Kovshenin
January 15th, 2013 11:10 amPost meta is not different in that respect. You can have two meta fields with the same key, one saying smoking, and the other one saying non-smoking. It’s up to the developer to create a good interface, and handle the proper use cases during submission when posts are saved.
Daniel Pataki
January 15th, 2013 11:24 amI agree and disagree at the same time :) It definitely depends on the scenario! In the specific example, I was thinking about an apartment management system. An apartment can be:
- smoking/non smooking
- Pets allowed
- Close to center
And so on. Taxonomies in this sense are always boolean.
If you want to add the distance of an apartment from the town center, that’s when I would use custom fields. The reason I would use custom taxonomies in many cases is that there is less hassle with the programming, you don’t need to create the controls manually.
Jack Neary
January 15th, 2013 2:24 pmI see your points, it’s is indeed true that you don’t have to create the controls when using a taxonomy, but at the same time, the controls that are generated for you are not ideal, I would even say not fit for purpose*, so I’m not sure about the weight of this point.
Following that, because the controls are not ideal, additional work will be required else where, the template and query would need further checks to identify and appropriately handle the scenario of multiple terms being assigned to the post, else you may end up with a property being listed as a result for both smoking and non-smoking queries, and also potentially displaying both terms in the listing it’s self also.
There is also the issue of unnecessary functionality, by using a taxonomy for each of these you’d be adding three additional sub menus to the post type for term management, which in a boolean scenario, simply isn’t required. This isn’t a big deal if your running the site your self, but if you’re selling it, I’d definitely want to avoid this, more menus, more questions, more problems*.
If this were a site I would be running my self, or for some one I know to be quite competent, I may cut a few corners* and use taxonomies, but for a proper client, I’d personally go the post meta route, a couple of radio buttons isn’t to hard to code up :)
I realise this has left the focus of the article some what, so I’ll leave it here. :)
Thanks again!
* My opinion only :)
Daniel Pataki
January 16th, 2013 7:37 amI completely agree with this part of your comment. I agree that if you’re working for a client you shouldn’t cut corners like this, but for personal work or for smaller jobs I think it’s fine.
Coding with WordPress for high volume / high profile clients is usually a completely different thing, perhaps it would merit an article?…
Konstantin Kovshenin
January 15th, 2013 11:05 amI think taxonomies are faster to query, because you won’t have to
CASTthe meta value and thus perform a full table scan. Also,wp_term_relationshipshas an index on( `object_id`,`term_taxonomy_id` )so it has to be much faster than meta anyway.Daniel Pataki
January 15th, 2013 11:28 amThat is a very good point. It should still make sense semantically, but I agree, taxonomies should be much faster. Especially if you’re looking at a large dataset.
fullbusy.com
January 15th, 2013 8:14 amI thought the whole concept was simple with word press, didn’t know we have this much complex coding in between. This is a lot of information.
Daniel Pataki
January 15th, 2013 11:26 amComplex functionality requires more complex code. It is still pretty simple and clear. It is a programmers job to make it simple and seamless for the user and WordPress gives you pretty good tools to do it.
Aldo
January 15th, 2013 10:49 amRegarding the paragraph “The same loop using wp_query”, I would to say that, when you starts a custom loop using the WP_Query class, there is no need to temporarily store the $post variable into another variable. In fact, at the end of the custom loop, you can easily restore the content of the $post of the main loop using wp_reset_postdata(). In other words, there is no need of this:
because the content of the $post of the main loop is not forgotten… unless you need to recall something of the original $post variable into the custom loop.
Also, take note that:
a) to reset the $post populated by WP_Query you should use wp_reset_postdata();
b) to reset the $post populated by query_posts you should use wp_reset_query().
Tom J Nowell
January 15th, 2013 12:04 pmYou should remove the part about resuming business and using a temporary variable to store $post, and replace it with a paragraph on wp_reset_postdata();
Every moment it’s up there is another question from a confused rookie dev on stack exchange.
Also you should modify your code examples to use consistent coding style, some use shorthand others use the braces style.
Ajay Patel
January 15th, 2013 9:40 pmReally informative article for wp newbi .
I am still sucked in to fetch information form metadata, where data stored in serialized format.
Let me know any one have idea in this.
mths
January 16th, 2013 1:19 amI’m not one to compare cms’s all the time, but this sounds like the beginning of WordPress’ own views module. It could do with an interface too.
Ali Raza
January 16th, 2013 5:39 amyeah , its really nice narration about WP_Query function. Also love the way the explanation of this particular author ;)
Thanks!
Gomy
January 17th, 2013 2:21 amIs there any method to sort posts by discount value between two different meta keys?
Example:
meta_key = price
meta_key = lowprice
discount = (price – lowprice) / price * 100)
I want to order posts by this result. :)
gonzalo
March 8th, 2013 10:37 amAmazing post!!
One thing, In the example about apartment(s), is there a way of making the query dynamic?
I want to use a couple of dropdowns and a “filter” button. So I can choose ’2 or more bedrooms’ + ’1 or more bathroom’, etc.
Can somebody put me in the right path? (I have almost no knowledge of php)
Thanks