Modifying the current query with pre_get_posts

Pre_get_posts is a hook

pre_get_posts” is a WordPress hook that allows you to modify the current query. Just a quick reminder: when you’re visiting any page of a WordPress install, the system creates a query that depends on the url you’re visiting. This is basically what the template hierarchy does.  The “pre_get_posts” hook is called after the query variable object is created, but before the actual query is run. That’s why this allows you to modify the query before results are printed on your screen. That’s pretty useful to modify for example an archive page, the results of a search, or a custom post type page. Here’s an info about “pre_get_posts” in the codex:

The pre_get_posts action gives developers access to the $query object by reference (any changes you make to $query are made directly to the original object – no return value is necessary).

That’s important to note that the $query object passed to your function is passed by reference. That’s to say that you don’t need to declare globals variables and you don’t need to return a value. Any changes you make to the object from inside your function are made to the original immediately.

Targeting the main query

Most part of the time when you access a page on a WordPress website, you’re loading more than queries, thats why it’s important to be aware of the query you want to modify when using “pre_get_posts”. A great method to know it, is to use the is_main_query() update: $query->is_main_query(). Here is what the codex says about is_main_query() function:

This function enables someone to hook into `pre_get_posts` and modify *only* the main query. This is a boolean function, meaning it returns either TRUE or FALSE. NOTE: admin screens also have a main query and this function can be used to detect it there.

NOTE: Mark Jaquith explained me via Twitter that the is_main_query() function must not be used anymore as it’s useless, and the conditional statement should be done through $query->is_main_query();

Here is a small example using is_main_query and pre_get_posts:

This code is simply taking off posts from category ID 5 from the main query.

Going a bit further

But you can go further, you can for example query only posts from a specific type and with a custom meta field key equal to a determined value. In the code below, we are targeting only custom post type called “portfolio” where meta key “project_type” is set to “design”, that’s a typical query that can be used on a portfolio page:

That’s not all, you can for example define a posts per page number:

You can also define a query that lists posts from a specific date:

And finally here is a sample to display posts older than the current date. This one is a bit more complex, because we need to use the “posts_where” filter:

Conclusion

As we saw, “pre_get_posts” is really powerful and can really helps you to customize the current query. This is also a good method to modify a query made from a plugin without modify the core plugin files (or the theme core files). So i really encourage you to have a deep leep to that hook and use it in your own developments!

13 responses to “Modifying the current query with pre_get_posts”

  1. Gilles Vauvarin

    Hello Rémi,

    Thanks for this short tutorial.
    Correct me if I’m wrong but actually the main interest to use pre_get_posts() in our code is to avoid to call an unnecessary new query and thus decrease the number of request on our data base ?

    1. Hi Gilles, not exactly. Actually, you can increase the number of parameters to send to the query, not just resuming the query. So, the main aim is to modify the query by adding or deleting parameters.

  2. Grant

    Thanks for the post Remi,

    I’m having a bit of trouble using your method vs. modifying the WP $query directly, wondering if pre_get_posts is what I need or not.

    I’d like to filter out posts in a specific archive page where the value for a custom meta field is earlier than todays date. Here’s my code: http://pastebin.com/fS1CyuRR, which works when modifying the WP $query directly, but not when using your pre_get_posts method.

    I thought I’d pose the question to see if you have any thoughts! Thanks!

    1. Hi Grant, i don’t think you need to use pre_get_post for what you need. I guess that a simple new WP_Query should do the job.

  3. Grant

    Thanks Remi, got it working with a new WP_Query that sets the tax term dynamically: http://pastebin.com/nzBWd5tL

  4. Julien

    Hi there,
    Ok this pre_get_posts might be what I am looking for.
    Here is a short description of my need:
    > I am using Groups plugin to assign specific products (woo commerce) to specific users through capabilities and user groups. So far it works great BUT the plugin only affect the VISIBILITY of the products in the loops depending on the current user groups_capabilities.
    > The problem is that it doesn’t change the query so all the counters in frontend (next to woo category widget for instance) are wrong, AND it makes the user navigating on a site with an overlarge query instead of only its assigned products (or any type of posts).
    > So I’d like to tell all the loops in WP, including the Woo Shop loop, frontend and backend, to apply the following parameter:
    Posts>groups-groups_read_post */post restriction meta key which stores the capability the user needs to be able to read */ = “current user”>wp_groups_user_capability>capability_id>wp_groups_capability>capability */field that stores the corresponding value to the one in the post */
    Obviously I do not know how to code.
    But the rule above is quite simple (or not?): the capability assigned to a product must match the one of the connected user in order for this product to be queried.

    Your help is greatly welcome, urgently for the shop loop. And for I can’t code I can pay for my ignorance and your lines :)
    Thank you
    Julien

    1. Hey, i think the best thing is to create a ticket on http://support.woothemes.com, as i can’t provide support for WooCommerce here.

      1. julien

        Yep, this is the best way.

        Thank you

  5. Joel

    Thanks Remi, this was really useful for modifying the query on an archive page to order by a custom meta value.

    While the code works brilliantly i’m confused as to why != is used to specify the post type? Surely we’re excluding, not including the post type here. Am i being daft?

    $query->query_vars[‘post_type’] != ‘portfolio’

    should be $query->query_vars[‘post_type’] == ‘portfolio’

    ? Although Worpdress agrees with you, i’m confused as to why..?

  6. TheCologne

    Hey Remi,
    thanks for this nice tips!
    Maybe you can help me with a little part of code.

    is_main_query() ) {
    $query->set( 'order', 'ASC' );
    add_filter( 'posts_where', 'rc_filter_where' );
    }
    return $query;
    }

    // Filter posts older than today
    function rc_filter_where( $where = '' ) {

    $today = date( 'Y-m-d' );
    $where .= " AND post_date >= '$today'";

    return $where;
    }

    But I need to order by my custom field called previewDate. This is a date the event takes place.
    With your script I like to filter all events that are >= the $today date.

    Do you have any advice how this works?

    Cheers & thanks,
    Denis

  7. Tcmo6

    Hello Remi

    Is there any way in woocommerce to display the out of stock products last on any listing (archive, category, featured, etc) using this method?

    Thanks!

  8. […] Pour en savoir plus sur pre_get_posts, je vous recommande l’excellent billet de Rémi Corson. […]

  9. […] Et l'ajout de nouvelles routes et paramètres d'URL permettant de filtrer vos pages d'archives est aisément possible grâce à add_rewrite_rule et pre_get_posts. […]

Leave a Reply