An In-Depth Guide to Conquering WP_Query
An In-Depth Guide to Conquering WP_Query
Getting posts from the WordPress database is one of my favorite topics. The flexibility of the WP_Query class – which allows you to do this – is awesome, it enables all the fancy CMS-like features you see on the front-end.
Do you need to list all tasks entered by a certain user between two specific dates? No problem! Do you want to list all scheduled posts and pages in a specific category, but only if it was modified less than two weeks ago? You can do that, too!
WP_Query class is easy to understand and easy to implement. In this comprehensive tutorial – complete with lots of example code snippets – we’ll look at how you can use the class to make WordPress do your bidding.
What is a WordPress Query?
Let’s back up a bit and talk about queries. In general, when someone says query they mean a query to the database – we ask it for some information. This can be anything from all the phone numbers of all our users to all the categories created.
When referring to “a query” or “a WordPress query” we usually mean a query that retrieves some posts for us. Almost all WordPress pages create a query for you on their own. On the index page (the main blog view) WordPress queries the database for your latest posts. On a category archive page the latest posts within the category are retrieved. On author pages the latest posts from the author are pulled, and so on.
Even single posts and single pages use a query. In those cases they always return one result, but query it is nevertheless. So almost any default page you load contains a query that is performed automatically.
The Loop: How Queries Are Used
It’s one thing to retrieve a bunch of posts, and it’s a whole other matter to actually display them. Each theme uses different HTML and styling elements to do this, but what they all share is “the loop.” The loop simply refers to a WordPress mechanism that steps through the retrieved posts, enabling themes to display them. It looks something like this:
OK, so this isn’t just a loop, it’s also an if/else statement. It is a simplified example of what you might find on any page which lists a bunch of posts. First we use
have_posts() to check if any posts are returned. Then, as long as there are posts we create an element for them and output the title and the content. The
the_post() function is responsible for processing some more post data and incrementing the counter. At the end we display some text if there are no posts to show.
Before we move on, let’s clarify some terminology. The term “the loop” is used to describe the loop on the page which lists the posts pulled in by WordPress by default. Let’s look at an example to clarify this. If you scroll to the bottom of an article right here on WPMU DEV, you’ll see a related articles section:
This section lists posts and uses a custom query and a loop to do so. This is not considered “the loop,” however, because the items within it aren’t the ones WordPress lists by default on this page. If you have multiple queries and loops on the same page they are usually referred to as secondary loops and custom queries.
Creating Our First Query
Let’s create our first custom query. Let’s say we want to list posts from a particular author in a particular category. Here’s one way to do that:
The first part of the code above deals with querying a database. As you can see I’ve instantiated a new
WP_Query object by passing it some arguments. Don’t worry if you don’t understand the fancy object oriented terminology here because using it is very straightforward. I passed my user’s nicename as the
author_name parameter and the category’s slug as the
To list these posts we need to modify our loop slightly. Instead of using
the_post() we use them as methods of the
WP_Query object. What this means in practice is prepending
$variable_name-> before these functions.
In future examples I won’t show the loop but the idea is always the same. If our variable is called
$custom_posts you’ll need to use
Creating custom queries is all about using the parameters. How to get posts from particular dates, authors, custom post types, custom taxonomies, statuses and more. Let’s take a look at all the parameters with some basic examples for each.
There are four separate parameters you can use to grab authors. The
author parameter accepts a single author ID or a comma separated list of author IDs. Using negative numbers will result in that author ID being excluded.
author_name is a parameter you can use if you want to pass the author’s nicename instead if his/her ID. Take care as this is not necessarily the same as his/her username.
The two remaining author-related parameters are
author__not_in. These were added later in WordPress 3.7 enabling much-needed array support. With their help you can supply your author needs as arrays.
This doesn’t seem like much of a change from using the
author parameter but from a modularization and best practices point of view it is a world apart.
Use Case: Listing Articles From Top Authors
Let’s take a look at how you could approach listing articles by top authors. Let’s assume that you have a rating system in place. Each author’s global rating is stored in the user meta table with the key of
rating. We can combine the
get_users()function and a custom loop to grab the posts of authors with the highest ratings.
Restricting posts based on categories can be done with no less than five separate functions. The
cat parameter behaves just like the
author parameter from above, accepting integers to include and negative integers to exclude:
category_name is horribly named because it actually accepts a category slug. You can usually guess a slug from the name by converting all letters to lowercase, omitting all special characters apart from underscores and dashes and turning spaces into dashes. A category named “Book Reviews” will likely have the slug: “book-reviews.”
You can use arrays of categories with three parameters.
category__not_in behave just like their author counterparts. In addition to these you can use
category__and, which will make sure that only posts that are assigned all categories given are retrieved.
Use Case: Get Articles from Your Most Used Categories
In this scenario we’ll retrieve a list of categories based on the item count within that category. We’ll feed this to our query to get posts from the top 3 most frequently used categories.
You’d think that tags would have the same set of arguments, and you would almost be right, but there are two extra here.
tag__not_in should be familiar by now, let’s look at some quick examples:
I’d like to point out two things. First of all, the WordPress core is inconsistent.
category expects an ID,
tag expects a slug,
category_name actually expects the slug and so on. The takeaway here is that you should always strive to make things as consistent as possible but everyone makes mistakes. Sometimes you are forced to make them for legacy reasons, for example. Take criticism with a pinch of salt!
The second thing I wanted to point out was that you should always use array when possible. Even though the
tag parameter allows you to handle multiple tags, I recommend using
tag__in and the other array-based ones.
To make things even easier tags have an additional two parameters:
tag_slug__in. Apparently there is no
tag_slug__not_in. Don’t ask why! These behave as you would expect them to:
Use Case: Combining Tags, Categories And Authors
Since we know a few parameters now, why not start combining them? Let’s take two specific authors and show everything they’ve written that has been featured (by adding the “featured” tag to it) in the “Books” or “Movies” category.
WordPress has a number of built-in post types: post, page, attachment, revision and navigation menu item. It has become commonplace to add custom post types for managing projects, your portfolio, products, forums and so on. Using the custom post type parameter
post_type you can narrow down your posts based on post type.
When a string is passed to this parameter it will look for posts with the given post type only. If an array of post types is passed, posts will be returned for any matching post types.
Note that this parameter has a default value, which is
post. If you want to return other post types you must specify them explicitly. If you are using the
tax_query parameter (more on this later) the default value becomes
Just like we did for post types we can specify a single post status or multiple post statuses to pull posts from. The
post_status parameter takes a string if a single status is used or an array if you want to select from more than one. WordPress has eight built-in statuses:
publish– a published post or page.
pending– post is pending review.
draft– a post in draft status.
auto-draft– a newly created post, with no content.
future– a post to publish in the future.
private– not visible to users who are not logged in.
inherit– a revision. see get_children.
trash– post is in trashbin (available with Version 2.9).
You can use
any to indicate that you want to include all post statuses. The default is
publish. Make sure to specify if you want something else.
Use Case: Sneak Peeks Of Upcoming Products
If you run a bookstore and you know that a potential bestseller is coming out soon you could use a custom WordPress query to list them. No need to publish these posts and then use some special trickery to exclude them from view until they can be ordered, just list scheduled posts directly:
The Search Parameter
The horribly named
s parameter takes a string, which is used to search within your posts. Make sure to URL encode your search string (you can use PHP’s
urlencode function) to account for spaces and other special characters.
Use Case: Searching Post Subsets
A search of your whole content can be performed from the default search widget so using the
s parameter is most useful when combined with other parameters. The following examples allow you to search various subsets of posts:
Password Protected Posts
In the publishing settings box you can choose to password protect a post. This is great for protecting private content or building a subscription model, perhaps. Whatever the case may be, you can use the
has_password parameter to specify whether or not you want password protected posts listed.
If the value of this parameter is
true the query will grab posts that have passwords associated with them. If false you will get only those posts that do not have passwords. If the parameter is set to
null or omitted all posts will be shown. You can use the
post_password to list posts that are protected by a specified password.
Use Case: Online Treasure Hunt
You can create a fun game using WordPress. Users must de-cypher a puzzle, getting a password for a list of posts, which contain clues to the final solution. The user must put the password in a form. Once submitted it takes the user to a page, which uses the submitted password as a query parameter, something like this:
By using the full loop from the beginning of the post you can make sure the user gets a “no posts found” message if the incorrect password is given. If the correct one has been entered, a list of posts will be shown protected by that password.
Including, Excluding and Targeting Specific Posts
There are nine separate parameters for grabbing one or more specific posts.
p is the parameter you can use to grab one specific post-based on its ID. To use a page slug you can pass a string to the
pagename accomplish the same task, but for pages these two parameters are a bit confusing. If you pass the ID of a page to the
p parameter, it will not work! On the other hand, even if you don’t set
page_id will work as expected.
Think of it like this: When you use
name it is as if the post type was set to post. When you use
pagename it is as if the post type was set to page.
post__not_in params work similarly to the ones we saw for categories and tags. They accept an array of post IDs.
post_parent parameter takes a single ID. Only posts, which are child posts of the given ID, will be listed. The
post_parent__not_in parameters take an array of post IDs – we’re used to this convention by now.
Keep the post type restrictions in mind when using these parameters. If you include the IDs of multiple post types you will need to specify
any as the post type, or the exact types you are looking for.
Use Case: Get All Post Attachments
If you’d like to list all images and other attachments of a post you can use the parent parameter to your advantage. This is perfect for auto-generating galleries or download lists.
Now that you have a good understanding of parameters and how they are used, it’s time to look at a more advanced one:
This parameter is itself an array, which you can use to create more complex conditions and use custom taxonomies. Let’s take a look at it through an example, courtesy of the WordPress Codex.
Our taxonomy query has two conditions and a relation. The relation dictates whether at least one condition (OR), or all conditions (AND) must be true. The conditions themselves are arrays that define the taxonomy restrictions.
The first condition states that the posts we are looking for must have the movie genre action or comedy assigned. The second condition states that we are looking for all posts, except for those associated with the three listed actors.
Since both conditions must be true (due to the AND restriction), this query will result in all action and comedy movies that star actors other than the ones listed.
Each condition has five parameters of its own:
taxonomy– where you specify the taxonomy slug
field– choose between
terms– use a single integer or string or an array of integers or strings
operator– how multiple terms are handled.
INwill allow posts belonging to any of the listed terms.
NOT INwill allow any posts that don’t include any of the listed terms.
ANDwill only allow posts that include all of the listed terms>
include_children– Wether or not to include children for hierarchical taxonomies
Use Case: Advanced Content Filtering
Taxonomy filters have a wide range of uses. From dealing with custom product taxonomies to creating an IMDB-style movie database you can do a lot with them. Here’s another example that allows for advanced book filtering:
1.6 million WordPress Superheroes read and trust our blog. Join them and get daily posts delivered to your inbox - free!
In this particular case the user wants to view all books which are a bit more upbeat (don’t contain the negative tags we’ve given) but they can’t be from the comedy or romance genres.
Meta queries are similar to taxonomy queries in structure. They allow you to query posts based on their values in the post meta table. WordPress offers four parameters here which can be used (
meta_compare) but can be substituted with
meta_query which is far better. Let’s look at an example:
This example shows a meta query which returns books that are over 500 pages in length, the syntax is quite easy after the taxonomy query. Let’s look at the subparameters you can use to specify what you want:
key– The key of the custom field
value– The value you are looking for
compare– The value comparison operator. Possible values are ‘=’, ‘!=’, ‘>’, ‘>=’, ‘<‘, ‘<=’, ‘LIKE’, ‘NOT LIKE’, ‘IN’, ‘NOT IN’, ‘BETWEEN’, ‘NOT BETWEEN’, ‘EXISTS’, and ‘NOT EXISTS’
type– The data type of the field. Possible values are ‘NUMERIC’, ‘BINARY’, ‘CHAR’, ‘DATE’, ‘DATETIME’, ‘DECIMAL’, ‘SIGNED’, ‘TIME’, ‘UNSIGNED’
Don’t forget the relation parameter, which you can use to indicate the relationship between the separate query clauses – it works just like in the taxonomy queries above.
Use Case: Posts With Featured Images
There are tons and tons of ways to use meta queries. One great example is grabbing only posts with featured images. The ID of the featured image is stored in the post meta table using the
_thumbnail_id key. We can use a meta query to check for posts where the value of this meta key is not empty.
Dates have a similar history to meta queries.
WP_Query offers 8 parameters to narrow down your post list by date, all of which can be replaced with the
date_query. I’ll be looking at
date_query exclusively because using it is better practice.
Date queries can be a bit more complex, let’s look at the parameters before we get into the examples. Here is the full list, courtesy of the WordPress Codex:
year– 4 digit year (e.g. 2011).
month– Month number (from 1 to 12).
week– Week of the year (from 0 to 53).
day– Day of the month (from 1 to 31).
hour– Hour (from 0 to 23).
minute– Minute (from 0 to 59).
second– Second (0 to 59).
after– Date to retrieve posts after. Accepts strtotime()-compatible string, or array of ‘year’, ‘month’, ‘day’ values:
yearAccepts any four-digit year. Default is empty.
monthThe month of the year. Accepts numbers 1-12. Default: 12.
dayThe day of the month. Accepts numbers 1-31. Default: last day of month.
before– Date to retrieve posts before. Accepts strtotime()-compatible string, or array of ‘year’, ‘month’, ‘day’ values:
yearAccepts any four-digit year. Default is empty.
monthThe month of the year. Accepts numbers 1-12. Default: 1.
dayThe day of the month. Accepts numbers 1-31. Default: 1.
inclusive– For after/before, whether exact value should be matched or not.
compare– See WP_Date_Query::get_compare().
column– Column to query against. Default: ‘post_date’.
relation– OR or AND, how the sub-arrays should be compared. Default: AND.
That’s a bit of a mouthful but it does allow us to use dates with considerable flexibility. By using the first seven parameters you can target date ranges all in one go:
To have more control over date ranges you can use the
after parameters. You’ll need to specify the year/month-day as sub-array members. Use the
inclusive parameter in conjunction to specify wether or not the boundaries should be taken into account.
This example (from the Codex) will return all posts between January 1st 2013 and February 28, 2013. Since
inclusive is set to true, posts on the boundary date will also be returned.
Date functionality here is based on the
WP_Date_Query class. I recommend giving it a read for a more thorough understanding – it can do quite a lot!
Use Case: Seasonal Past Posts
Many websites have seasonal content: Christmas posts, New Year’s roundups, Valentines Day stuff and so on. By using a custom date query you can add relevant time-sensitive content to your website from your archives.
The short example below will pull posts from April 1st, spreading April fool’s cheer perhaps?
I wouldn’t blame you for thinking that we’ll be looking at one parameter here or perhaps two. In reality there are no less than seven parameters for handling pagination.
The most basic one you will probably use is
posts_per_page, which essentially determines the number of posts to pull from the database. Setting it to -1 will grab all available posts.
paged is a frequently used parameter, it determines which page of results show. It is used in conjunction with
posts_per_page. If 10 posts are shown per page and the
paged parameter is set to 4 your fourth set of 10 posts will be returned.
nopaging allows you to ignore paging completely if it is set to true.
You may also find yourself in need of the
offset parameter which works just like an offset in MySQL – it offsets the posts retrieved by the given amount. This tends to break pagination unless you know what you’re doing, I would use this sparingly.
If you find yourself bothered by sticky posts you can use
ignore_sticky_posts to ignore them. By default sticky posts are placed at the top of the list, as well as the natural location they would be in. To disable this behaviour set this parameter to false.
posts_per_archive_page parameter sets how many pages are shown on archive pages. These would be all pages where the
is_search() functions return true. If this parameter isn’t used the
posts_per_page parameter is used.
page parameter shows the posts that would normally show up just on page X of a Static Front Page.
The example above returns the second set of 20 posts from the database while ignoring sticky posts.
When listing your results, the order in which they are shown is just as important as what is returned. Two parameters give you control over this:
orderby parameter determines the order in which posts are returned. There are quite a few options, let’s take a look:
none– No order (natural MySQL selection order)
ID– Order by post id
author– Order by author id
title– Order by title
name– Order by post slug.
type– Order by post type
date– Order by date
modified– Order by last modified date
parent– Order by parent id
rand– Random order
comment_count– Order by number of comments
menu_order– Order by Page Order
meta_value– Order by the meta value
meta_value_num– Order by numeric meta value
post__in– Preserve order given in the
Almost all of these are self explanatory, let’s take a more detailed look at ordering posts by meta value. To get this to work you need to specify the
meta_key parameter as well so WordPress knows which key to pull meta values for. You should also take care to specify the correct type since ordering lead to unexpected results otherwise.
if you are ordering regular text you should be fine. If you are ordering numeric values, use
meta_value_num instead of
meta_value. Alternatively you can specify the
meta_type parameter which allows you to specify the data type. See the
meta_query section for data types you can use.
order parameter determines the direction of the ordering. Use
ASC for ascending,
DESC for descending. Take a look at the examples below for a practical look at how ordering can be done for various use cases.
For the sake of thoroughness I thought I’d mention the option to order by multiple columns. Pre 4.0 you could do this using a space separated value for the
orderby parameter, like this:
orderby => 'title menu_order'. Since WordPress 4.0 this was improved, you can now use an array and specify the order separately:
Use Case: Order Products By Price
A frequently needed feature in eCommerce themes is the ability to order products by price. In the example below we look for products over $20 and list the results by price, lowest to highest:
It seems that the
fields parameter is not well-known, even though it is one of the most useful features. You can pass two values to this parameter or omit it. If omitted you will end up with post objects as usual. If you pass
ids an array of post IDs will be returned. If you pass
id=>parent an associative array of ID – parent relationships will be returned.
The reason I think this is such a useful feature is optimization. Some plugins use
WP_Query to grab post IDs for galleries or other shortcodes. Many do not utilize the return parameter and end up giving the server a lot more work to do – work which is never used in the code. If you just need IDs make sure to use the
Use Case: Programmatically Placing A Gallery
If you want a gallery after each of your posts, showing all the images in that post you can utilize a query in tandem with the gallery shortcode.
First, we grab all attachments from the post, making sure to return the IDs only. We then implode this array to make a string containing a comma separated list of IDs we can use in the shortcode. Finally we use
do_shortcode() to create the gallery.
This one is a bit obscure and not implemented very modularly. The idea is that if you use the
perm parameter and supply
readable as the value, only posts which the current user can read will be shown. This is useful if you want to list regular and private posts but make sure to only list those private posts that the user has access to.
This one is for advanced optimization – it allows you to control the caching behaviour of queries. Generally it is a good idea to add results of a query to the cache, chances are it will be used again and again in the same form. Your list of top 10 posts is not likely to change from query-to-query for example.
There are two cases where you may want to control what is cached. If you list posts without needing their metadata or taxonomy data you can use
update_post_term_cache to disable the respective cache. If you are just doing a one-off query for a quick test you can also use
cache_results to disable caching of the post results themselves.
One instance where I’ve used this was creating a quick export table of all the posts in the database. The export was essentially a CSV file of all post IDs, titles, categories and tags associated with the posts. Caching this one-off query makes no sense, especially since there were 8,000 posts with lots and lots of meta data.
So far we’ve been looking at the parameters we can pass to
WP_Query. Since we are talking about a class here, not a simple function there are properties and methods we can use to manipulate it. The WP_Query Properties Documentation has a full list of properties. I’ll show you some of the more useful ones and how to use them, in case you aren’t familiar with properties.
I personally tend to use
$query_vars holds the array that was passed to the class, it contains all the parameters we used when making our query.
$found_posts property holds the number of posts found in total that match our query. The
$post_count property holds the number of posts currently being shown. These are usually not the same since pagination is being used. There may be 52 posts found, but only 10 are displayed currently.
Beware as the
$post_count is also not necessarily the same as the value used in the
posts_per_page argument. If 52 posts are found and 10 are shown per page the last page will look like this: 52 posts found, 10 posts displayed per page but currently, only two are being shown.
$max_num_pages holds the number of pages needed to display the results. This is basically the result of
$found_posts / $posts_per_page rounded up to the next whole number.
When talking about classes, properties are just like variables, methods are just like functions.
WP_Query has a number of methods, although you won’t really need to use them directly in most cases. The two most important ones you already know are
the_post(). Take a look back at the beginning of the article for how these work.
There are some useful functions like
rewind_posts() which essentially resets the loop. You may want to use this if you are outputting the exact same list in two separate locations.
set() methods allow you to get and set a specific query variable. Again, these are usually not used by themselves only in special circumstances. Take a look at the Method Documentation for more information.
Data Safety: Validating, Sanitizing And Escaping
Custom queries are frequently paired with front-end forms. A typical scenario may be a custom-coded filter system allowing the user more access over the post list. Take a look at our Adding Post Filters To Your WordPress Site article for an introduction.
If you are accepting any form of user input you should always escape the data to prevent SQL injection attacks and other headaches. Take a look at the excellent Validating, Santitizing And Escaping User Data article in the Codex for more information about this topic.
Modifying the Raw SQL Query
There are a number of filters that allow you to hook into the actual SQL query that is performed upon the use of
WP_Query. This is rarely needed so do try and work with the given parameters. More often than not (way more often) you can do everything you need since this class is pretty powerful.
Filters such as
posts_join allow you to modify specific clauses in your SQL statement. While
WP_Query is flexible, it doesn’t currently allow for very complex WHERE clauses involving multiple taxonomies and multiple relations. In these cases you could use these filters to modify the query directly.
The WP_Query Filters section is a good place to start but you will need considerable SQL knowledge to utilize these filters properly.
This article hopefully gave you a better understanding of the
WP_Query class and just how useful it is when dealing with post lists. Its use doesn’t stop at listing posts – you can combine the class with custom post types, taxonomies and meta data to build a powerful and useful CMS system.
If you’re interested, there are a few classes which work similarly or are used within
WP_Query and other sections of WordPress. WP_Comment_Query can be used to grab comments in much the same way as
WP_Query. WP_Meta_Query handles all the meta queries and WP_Date_Query handles all the date restrictions for both classes and will probably be used in upcoming parts of the WP ecosystem.