Performant WordPress Queries

Querying posts can be tricky. There are many ways to fetch content, and it’s not always clear what will give you the most benefits. The answers I’ve seen are also not always straight-forward and the “why” is not always explained.

Here’s my attempt to give basic advice on how to write performant WordPress Queries.


When doing a query, WordPress will want to calculate how many rows of results are possible for your query so that it can properly set pagination. That means even if you only ask for 1 post, WP_Query will still check all possible results to calculate the number of rows found.

Let’s imagine a few scenarios where that could get complicated:

“Get me the latest 5 weather reports”
What if you have a weather report every hour? Even if you limit the query to 5 reports, you might be having to thousands of rows of data just to get 5 of them.

This can get hairy quickly if there are a lot of rows to calculate. If you have a posts table with 100 posts – no problem! However, say you had 100,000 posts and you only wanted 5 – WordPress would add the SQL_CALC_FOUND_ROWS function to the SQL query to figure out pagination, creating a larger query load than needed.

Luckily, WP_Query lets you set the 'no_found_rows' => true argument to skip that calculation and save time.

If you’re using get_posts, it sets no_found_rows to true by default.

When you don’t need pagination, set 'no_found_rows' => true.


Do you use sticky posts? I sometimes forget they even exist. However, WordPress queries will check them by default unless you tell them not to.

What WP_Query will do is fetch all the posts requested, and if it finds sticky posts, it’ll loop through every one of them and reorder the array so they are at the top. If there are sticky posts outside of what was fetched, it’ll run another sql query to just grab those posts.

What does that look like for performance? It depends! Do you have a lot of sticky posts?

Say you want to get the latest 10 posts and you have 5 sticky posts that are older than those 10 posts – that means you are now doing 2 queries, getting a total of 15 posts, and then looping through each of them to reorder them. Not a big performance hit but still more work.

What if you were fetching 100 posts, and you had 20 sticky posts outside of that range?

You can see where this could get problematic.

Set 'ignore_sticky_posts' => true whenever possible to prevent extra queries or additional time spent reordering results.


If you’re doing a taxonomy query, include_children will be set to true by default. That means that if you’re querying for posts in a category of Parent and that category has Child 1, Child 2, and Child 3 – your query will grab posts for all of those categories and not just the one you defined.

Taxonomy queries are already not the most efficient because they’re querying multiple tables and doing a JOIN operation on the results so adding more complexity with children taxonomies could mean make them perform worse.

Set 'include_children' => false when doing a taxonomy query if you can.

Be specific

This is probably the most important part of efficient querying in WordPress (or any platform). You should be specific without being too complicated. In other words, get the smallest amount of data in the least complicated way.

'fields' => 'ids'

Say you want to display the featured images of 10 posts. Instead of doing a query for the entire posts and plucking the IDs out, just do a query for the IDs themselves with 'fields' => 'ids'!


Don’t abuse posts_per_page – try to stick with the minimum amount possible.

post_type and post_status

If you know the post_type and post_status you need, make sure to declare them! This could save a lot of time in the query.


Using a date_query can be really beneficial as well. If you want to grab the latest posts, instead of grabbing a large amount of posts and grouping by date – use a date_query to only get posts after a certain time. Here’s an example to only query posts from the past 6 months:

add_action( 'pre_get_posts', function($query) {
    if ( $query->is_main_query() && $query->is_archive() ) {
        $query->set( 'date_query', array(
                'after' => date( 'Y-m-d', strtotime('-6 months')),
        ) );

Advanced Post Cache

Advanced Post Cache (APC) is a plugin used on many WordPress platforms. It’s enabled by default on WordPress VIP. If you haven’t heard of it before, give it a quick read, it’s not very large!

What APC does is filter queries to cache them. For example, it will filter through posts_request to query the items returned.

If you’re using APC, make sure you set 'suppress_filters' => false. That will ensure the queries are filtered by APC and cached. WP_Query will set suppress_filters to false by default, get_posts() will set it to true by default.

There isn’t really a “one size fits all” approach here, it’s really more about learning to think about how you query data and how WordPress’ wrappers work so you can get the most out of them.


Leave a Reply