In the previous post, I talked a bit about using pre_get_posts and how to efficiently exclude categories from the main loop.

Of course, in posts like that, the code is meant to be a point of reference or a starting place for which more advanced functionality can be written.

But one thing that I failed to mention about using pre_get_posts in WordPress is that it’s not limited to the public facing side of the site.

Using pre_get_posts in WordPress

As mentioned in the previous post, whenever you hook a function into the pre_get_posts action, then you’re passed – by reference – the $wp_query object. As such, the code looks something like this:

function demo_exclude_category( $wp_query ) {

    // Add the category to an array of excluded categories. In this case, though,
    // it's really just one.
    $excluded = array( '-1' );

    // Note that this is a cleaner way to write: $wp_query->set('category__not_in', $excluded);
    set_query_var( 'category__not_in', $excluded );

add_action( 'pre_get_posts', 'demo_exclude_category' );

The thing about this particular function is that it will exclude posts from that category in every single place $wp_query is used.

This means that the results will be excluded from places such as:

  • The RSS Feed
  • Archive Pages
  • Search Results
  • The Dashboard
  • …and so on

To that end, you’re likely only going to want to exclude it from certain places. So say, for example, that you wanted to exclude categories except from the search pages, the archives pages, and the admin dashboard.

Then the code would look something like this:

function demo_exclude_category( $wp_query ) {

    if ( ! is_admin() && ! is_search() && ! is_archive() ) {

        // Add the category to an array of excluded categories. In this case, though, it's really just one.
        $excluded = array( '-1' );

        // Note that this is a cleaner way to write: $wp_query->set('category__not_in', $excluded);
        set_query_var( 'category__not_in', $excluded );

    } // end if

add_action( 'pre_get_posts', 'demo_exclude_category' );

And for those of you who are more particular about conditionals, you could also write it to be if ( ! ( is_admin() || is_search() || is_archive() ) ), but that’s neither here nor there, really, for this post.

I just figure it’d come up in the comments at some point :).

Anyway, if you’re interested in the conditionals that WordPress offers, be sure to check out the Codex article on Conditional Tags as they come in handy when working with things like manipulating the main query.


Join the conversation! 9 Comments

  1. why not just include the is_main_query into your setup?

    also, I tend to just include the check for admin first, like so:

    if ( is_admin() )

    and since I often use this on both the admin side and front end side, I’ll write a separate function for each area.

    • This was also mentioned in the comments yesterday, which I’m totally down with (what, you mean you don’t read every comment here?! ;)

      But seriously, that’s one of the conditional tags that works – and its great – but half the point of this particular post is also to expose some of the nuanced control you can have over your work given WordPress’ conditional tags.

  2. Also, I ran into some trouble with is_main_query in the admin panel like you mentioned. I was unable to sort through drafts/published/private posts before I figured out that I was blocking myself with pre_get_posts from the Codex ( Rookie mistake, I know, but I can’t be the only one. I like the idea of a separate function for is_admin instead of simply filtering out any admin queries with to ! is_admin though.

  3. Yikes! I didn’t know about this. There’s been a lot more talk and finger-pointing about using pre_get_posts and sadly this has been left out. Thanks for the heads up.

  4. Tom,

    I had no idea set_query_var() was a wrapper for $wp_query->set(). This will clean things up a tad.


    • Kind of… `set_query_var()` actually sets the query variable for the global `wp_query`. Whereas $query->set() sets if for the current WP_Query instance being passed (it might be a query for the navigation menu, related posts, posts in the sidebar etc). (Its slightly confusing as Tom is using the name `$wp_query` for the query being passed which most of the time won’t be the main query – `global $wp_query`).

      I prefer to use the conditional `$query->is_main_query()` and then use`$query->set()` to set query variable as what you are checking/doing is more obvious.

      Just a side note, in Tom’s example he’s modifying the ‘main’ query for every query. I.e. every time *any* query (on a non-admin, non-search/archive page) is run, `demo_exclude_category()` is excluding a category from the main query.

Leave a Reply