Cut custom query_posts time in half with pre_get_posts

Cut custom query_posts time in half with pre_get_posts

A Day in the Life of WordPress

Let’s have a look at a typical day for Mr. WordPress–hereafter referred to as “Big W.” Usually, Big W gets requests from web visitors, figures out what they want, fetches the right information from the database, and then serves it up in the right theme template. The scene goes something like this:

Web Visitor: I’d like to see, please.

Big W: Sure, let me get that for you.

Big W steps away to:

  1. Parse the request to figure out which post to fetch.
  2. Run a query to retrieve the post and data for building the page. (Technically, this is 4 different queries, but that’s another story.)
  3. Deliver the data to the right template page for the visitor.

Usually this is a smooth process Big W performs quickly. After serving up the data in the right template, he simply moves on to the next request.

Somebody poisoned the waterhole

But wait–trouble lurks around that template corner. Sometimes, developers add query_posts calls or build a new WP_Query  in their templates. They don’t mean any harm–they just want something special done with the data before it’s served up in the template. Little do they know, they’re causing headaches for Big W. Here’s how that chapter unfolds:

  1. Big W, default query results in hand, approaches the right theme page template.
  2. Wait, what’s this?! Near the top of the template, Big W reads:
    query_posts( array( 'meta_key' => 'color', 'meta_value' => 'blue' );
  3. Big W, cursing, crumples his query results and tosses them aside, shaking his head. He steps away to go run a new set of queries that include the template’s requested custom field values.

Where are your manners?

Gasp! The theme developer waited until Big W got to the page template before specifying the page should only show posts containing a custom field named “color” with a value of “blue!” Let’s see if we can get a word from Big W on his way to the new query.

Big W: You know, I realize some templates need a second query–maybe to feature a special section of featured posts–that sort of thing. But in this case, it’s the default post we’re looking at! Why couldn’t they just tell me they only wanted blue posts before I ran the query? I mean–this is just rude!

Using query_posts incorrectly has more consequences

A short time passes, and Big W returns with the new query results.

Big W: Okay, here are your new results. But you know what? I feel like you take me for granted, and don’t appreciate the work I do for you. I didn’t bother:

  1. Bringing back the data you need to paginate your results
  2. Setting different global variables the way you might expect. (I just left the results I set from my first query.)

If that screws up your pagination or some widgets–it’s not my problem! Good day to you!

How to improve our working relationship with Big W.

We need a way to communicate with WordPress to ask for special results before the default query is run. Big W will not have to redo his work, and our site visitor gets their pages faster.

Enter the powerful and dangerous pre_get_posts

If some theme template of yours needs to alter the default query to get the right data, we need a way to let Big W know before he comes knocking on the template file’s door. Using query_posts in the template file is like leaving a note on the door that says “Oh, I forgot to ask you to get a gallon of milk, too–please go back to the store.” So how do we put that note in Big W’s pocket, instead of on the template file’s front door? This is where pre_get_posts comes in.

The hook “pre_get_posts” is called before the default query is executed. We can assign a function to pre_get_posts, then, to provide Big W with any special instructions before running that default query. Here’s how it works.

// our function that alters the default query
function note_to_bigw( $query ) {
// make changes to the default query parameters
$query->set( 'meta_key', ‘color’ );
$query->set( 'meta_value', ‘blue’ );
// we have to tell pre_get_posts to leave the note for Big W
add_action( ‘pre_get_posts’, ‘note_to_bigw’ );

One size does not fit all

Great! Now we restrict our posts to only those with a custom field “color” whose value is “blue.” But wait–we just royally screwed up most of our site. The way we wrote this, every query anywhere on the site will use this new restriction. We didn’t want that! We only wanted that restriction when the visitor asked for it.

Be careful what you wish for

We need to be more specific in our note to Big W, specifying this custom field restriction should only happen in certain situations. I’ll cut to the chase and spit out the correct code here, with appropriate comments.

The following code will properly alter the default query, but only when the URL includes parameters to ask for a specific custom field and value. This code would work well in your theme’s functions.php file.

function note_to_bigw( $query ) {
// Do not affect queries for admin pages
if( $query->is_admin == 1 ) {
// stop if wp is working on anything but the main query
if( !$query->is_main_query() ) {
// stop if we are not working on an archive view
if( !$query->is_archive == 1 ) {
// retrieve field name / value from URL if exist
$custom_field = ( $_GET['field'] ) ? stripslashes( $_GET['field'] ) : '';
$custom_value = ( $_GET['value'] ) ? stripslashes( $_GET['value'] ) : '';
if( $custom_field ) {
// add meta key requirement -- we’ll return all posts with some ‘color’
$query->set( 'meta_key', $custom_field );
if( $custom_value ) {
// build meta value requirement -- we’ll return only blue
$query->set( 'meta_value', $custom_value );
// we have to tell pre_get_posts to leave the note for Big W
add_action( ‘pre_get_posts’, ‘note_to_bigw’ );

Sometimes a new WP_Query is okay

When your page needs loops in addition to the default, it’s fine to ask Big W for a new query. After all–you aren’t throwing away his original work, you’re just asking for some additional data.

Example: Your page shows a list of posts tagged “feature” at the top, as a navigational aid. Below this, the normal posts are to be displayed. In this case, you create a brand new query for the featured posts, use the results, and use wp_reset_postdata to get globals back for the default query results.