In WordPress, pre_get_posts
is an action that makes it possible to modify an existing WP_Query
, before that query is actually run. pre_get_posts
offers some solutions that are more performant than writing a custom WP_Query
, and enables solutions to other problems that would be quite difficult otherwise.
Let’s learn about pre_get_posts
!
What a WP_Query
Is
Almost every page on a WordPress site is created by looping through bundles of posts, which are fetched using
WP_Query
.
In programming, to query a database generally means to fetch specific results out of that database. For example, in a database full of person data, you might want a specific person’s name and age, and you’d write a query (in SQL or a similar language) to fetch the information you want.
A WP_Query
is a special type of query, unique to WordPress, that fetches posts from the WordPress database. WordPress posts are the blocks of content that make up a site’s blog articles, pages, media attachments, and more.
As we cover in our introduction to WP_Query
, almost every page on a WordPress site is created by looping through one or more bundles of posts. These post bundles are fetched by the default WP_Query
that WordPress runs for that page type, and may also be fetched by one or more custom WP_Query
s we write ourselves.
pre_get_posts
in a Nutshell
pre_get_posts
is an action that lets you modify aWP_Query
that is about to run.
pre_get_posts
is an action that lets you modify a WP_Query
that is “about to run,” meaning “about to ask the database for a post bundle.” Before the query can run off to the database and get its bundle of posts, we’re going to swoop in and change which posts the query will actually request. This order of events is how pre_get_posts
gets its name.
For example, the following pre_get_posts
example will cause your blog index page, which normally lists your most recent posts of type Post, to instead list your most recent posts of type Page:
add_action( 'pre_get_posts', 'wpshout_pages_blogindex' );
function wpshout_pages_blogindex( $query ) {
if ( is_home() && $query->is_main_query() ) :
$query->set( 'post_type', 'page' );
endif;
}
Pasting this code into the live theme’s functions.php
leads to a (rather odd) blog index with only Pages and no Posts. On the blog page of our agency site Press Up, that looks as follows:
So pre_get_posts
let us modify the blog index’s already existing default query, without us having to write a new WP_Query
ourselves. That’s pre_get_posts
in a nutshell.
When to Use pre_get_posts
Remember, pre_get_posts
lets you modify an existing query. So pre_get_posts
is useful when it’s best not to write a new query, but to change one that already exists. Here are a few examples:
Some Custom WP_Query
s are Easiest to Modify with pre_get_posts
Sometimes you need to modify a custom WP_Query
that you didn’t write. This most likely means a query from an external plugin, or from somewhere in WordPress core.
As an example, the “Recent Posts” widget is a part of WordPress core. Let’s say that we’re using “Recent Posts” on our site, and that we’ve just created a page called “Fundraiser.” On our Fundraiser page, and that page alone, we want the “Recent Posts” widget to only show recent posts that are tagged with “fundraiser.”
We could install a better “Recent Posts” widget that allows this, and then make a custom widget area exclusively for the “Our Fundraiser” page, but that creates quite a bit of clutter. Instead, let’s just conditionally modify the custom WP_Query
that “Recent Posts” uses. That process has three steps:
- Make sure we’re on the “Fundraiser” page,
- Make absolutely sure we’re talking to the custom query generated by “Recent Posts” (the hardest part), and
- Modify that query to our liking.
Here’s the code:
add_action( 'pre_get_posts', 'wpshout_fundraiser_recent_posts' );
function wpshout_fundraiser_recent_posts( $query ) {
// Do nothing if not on Fundraiser page
if( ! is_page( 'Fundraiser' ) ) :
return;
endif;
// Make sure we're talking to the WP_Query
// generated by the Recent Posts widget
if( ! is_main_query() &&
$query->query[ 'no_found_rows' ] === true &&
$query->query[ 'post_type' ] === NULL
) :
// Fetch only posts tagged with "fundraiser"
$taxquery = array(
array(
'taxonomy' => 'post_tag',
'field' => 'slug',
'terms' => array( 'fundraiser' ),
)
);
$query->set( 'tax_query', $taxquery );
endif;
}
A bit of a convoluted example, but still probably quicker and cleaner than the alternatives if this was a real project.
Modifying WordPress Default Queries
Most of the time you’ll use
pre_get_posts
on WordPress’s default queries, since you can usually modify other queries directly.
WordPress’s default queries are extremely important: they’re the queries that fetch the most recent Posts for your blog index, all Posts of a specific category for a Category archive page, all posts from a specific month for a month-based archive page, search results for a search page, and so on.
In the previous section, I stretched my brain to find a case where you’d use pre_get_posts
to modify a custom WP_Query
. However, most of the time you’ll be using pre_get_posts
on WordPress’s default queries, since you can usually modify other queries directly. (Almost every example on the Codex documentation for pre_get_posts
modifies a page’s default query.)
pre_get_posts
is the best way to modify a default query. Here’s an important example from last week’s article on custom post types, causing a newly registered custom post type, Courses, to show up on the site’s archive pages, which wouldn’t happen by default:
// Make Courses posts show up in archive pages
add_action( 'pre_get_posts', 'wpshout_add_custom_post_types_to_query' );
function wpshout_add_custom_post_types_to_query( $query ) {
if(
is_archive() &&
empty( $query->query_vars['suppress_filters'] )
) {
$query->set( 'post_type', array(
'post',
'course'
) );
}
}P
This code first asks if we’re on an archive page. If we are, it modifies the page’s default query to pull in not only Posts, but also Courses.
The alternative to this approach—modify whichever template uses the default query you want to change (index.php
, search.php
, etc.), remove the template’s default query processing, and then write your own custom WP_Query
from scratch—is both brittle and way too much work. For example, I’d have no idea how to write a single custom WP_Query
to cover all the different kinds of queries that get generated for archive pages.
Check out the Codex for lots of other commonsense examples that relate to hiding a specific post from search results, modifying the categories that the blog index pulls in, and so on.
pre_get_posts
is More Performant than Writing a New WP_Query
Use
pre_get_posts
instead ofnew WP_Query()
when possible, because this minimizes queries against the WordPress database.
In some situations, you could use either pre_get_posts
or a new custom WP_Query
to do what you want. In those situations, it’s faster and better to use pre_get_posts
, because you only make one trip—and not two—to the WordPress database. Remember, every WP_Query
is a new, complex query that will be sent to the WordPress database. One modified database query will process a lot quicker than two completely separate queries.
Now You Can pre_get_posts
Working with pre_get_posts
is a very helpful tool to have in your pocket if you’re running into queries you can’t modify. It’s also an excellent way to prevent writing unnecessary custom WP_Query
s when it’s both simpler and more performant to modify and existing query. This is especially true when you need to make changes to WordPress’s default queries, for pages like your blog index, archive, and search pages; that’s where pre_get_posts
becomes indispensable.
Thanks for reading!
Thank you for your post.
Shouldn’t you be using add_action(), rather than add_filter() in your examples? Technically I believe both will work, but according to the codex, it should be add_action().
e.g. add_action( ‘pre_get_posts’, ‘wpshout_pages_blogindex’ );
Hi Theo,
Thank you so much! I’ve (finally) fixed that. 🙂
[…] Practical Uses of pre_get_posts […]
Thank you for the article, explains the concept very clearly.