Software, Development, and WordPress

How To Efficiently Exclude Categories From The WordPress Loop

When it comes to writing custom queries in WordPress, `WP_Query` is the API to use. And I could be mistaken, but I do see a lot of people urging developers to avoiding using `query_posts` in WordPress in favor this newer-ish API.

But the thing is that `query_posts` still has its place in WordPress development namely in modifying the results of the data queried for The Loop when rendering blog content; however, one of the the biggest caveats is the performance that `query_posts` can have on the performance of the blog.

I’ve recently been working on a plugin where I needed to exclude posts from The Loop based on the category. At first, I was going to use `query_posts` but I ultimately hit a few snags, so here’s an alternative way to exclude categories from the WordPress Loop.

Exclude Categories From WordPress Loop


The WordPress Codex Documentation for query_posts.

First, here are a few nuances about the function that you need to know about `query_posts`. From the Codex:

query_posts a way to alter the main query that WordPress uses to display posts. It does this by putting the main query to one side, and replacing it with a new query.

But there’s a very important thing to note regarding this function:

It should be noted that using this to replace the main query on a page can increase page loading times, in worst case scenarios more than doubling the amount of work needed or more.

All of that to say is that when you’re working with `query_posts`, then you need to be really careful with the work that you’re doing because you could, at worst, exhaust your PHP memory limits and thus tank the entire site.

So what’s our alternative?

Working with pre_get_posts

`pre_get_posts` is a hook that’s available to us that accepts the global WordPress query by reference so that we can modify the query variables prior to having the query actually run.

So, for example, the basic function would look something like this:

function demo_exclude_category( $wp_query ) {
    // TODO
add_action( 'pre_get_posts', 'demo_exclude_category' );

Again, `$wp_query` is passed to `demo_exclude_category` by reference so we’re able to reference to modify the query variables however needed prior to having the query actually run.

But there are two ways to do this: Procedural Programming and Object-Oriented Programming.

Procedural Programming

Procedural programming is what you’re used to seeing in things like `functions.php` as well as various plugins. In short, all of the functions are prefixed with a unique identifier and do not reside with inside a class.

So let’s say we wanted to exclude the first category from The Loop using procedural programming in either `functions.php` or in a plugin.

Here’s what’d be required:

  • Define the function
  • Register the function to the `pre_get_posts` hook
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' );

Easy enough, right? In fact, the object-oriented approach isn’t much more complicated.

Object-Oriented Programming

In the case of object-oriented programming, it’s practically the same thing; however, you define the hook in the constructor and the method as a class function:

class Example_Plugin {

	public function __construct() {
		add_action( 'pre_get_posts', array( $this, 'exclude_first_category_posts' ) );

	public function exclude_first_category_posts( $wp_query ) {

		// Only remove them from the blog page - display them on Search and Archive pages
		if ( ! 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' );

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




Notice above that I’m actually only performing the exclusion on the homepage rather than on Search pages and on Archive pages.

Anyway, when I was attempting to do something similar with `query_posts` there was a clear performance impact whereas modifying the raw query object prior to having it execute appears to be a much better course of action.


  1. Carrie Dils

    Mainly just want to chime in and say I appreciate your selection for the featured image on this post. ;)

  2. Matt

    Thanks Tom, this was really helpful and timely for me.

  3. Shea Bunge

    You should *never* use `query_posts`, under any circumstances. I’m surprised that it hasn’t been deprecated, actually,

    • Tom McFarlin

      Truth be told, I agree with you but until it’s officially marked as deprecated, I don’t mind mentioning it and the Codex article as long as I also provide an alternative way of working on solving the problem at hand with a different solution.

    • Stephen Harris

      Actually I can think of one instance where it was the right thing to use :).

      Let’s suppose you have a custom feed with custom template – now for this you don’t need to – and shouldn’t – use query posts. But if you wanted to generate this feed on the admin side (admin download / send as email etc ) from a custom admin page. you can use `query_posts()` to populate `global $wp_query` and then use your custom template (which expects the the main query to be populated with results).

      There are other ways to do that, sure – but it works well. And no kittens are hurt :).

      But in general, I agree. Should not be used :).

  4. Stephen Cronin

    You might want to use is_main_query() in the if statement. I’ve melted WordPress by forgetting that…

    Also, if you want them to appear in your feed, you’d want to add ! is_feed().

  5. Paul

    I also add a check for is_main_query and ! is_admin, because pre_get_posts can act on the nav menu post type and I found that when I modified posts_per_page without checking is_admin, then it would also modify the pagination on the admin side

    • Tom McFarlin

      Same thing Stephen mentioned.

      Both of these are really good points to mention (and I have a post I’m about to publish regarding `is_admin`, as well).

      Thanks to you and Stephen for this. Good stuff to have here in the comments.

  6. Hassan

    I think taking the global $query_string var and merging an array of options with it then running a custom query would work the best.
    Dig the article image!

  7. Alberto

    Very usefull post.
    I think it’s better add a note in the post about the !is_admin() and is_main_query().
    Honestly i missed it the first time and only days later when i found myself where was the problem i’m returned here to check if someone other got the same problem. :D

    Sry for my bad English. ^^

    • Tom McFarlin

      No worries on your English – it’s fine!

      Thanks for the comment here, Alberto!

  8. Ali

    Thanks, this is much smoother way.

  9. Iurie Malai

    The demo_exclude_category() function exclude categories from the loop and from archives very well, but not from Recent Posts and not from Recent Comments. How to resolve this?

    • Tom

      Off the top of my head, I’m not sure – I’d have to wait until I had a specific use case to see.

  10. Ryan

    Once thing I noticed when I implemented this is it prevented the posts from showing in the admin panel too so I adding the conditional:

    if(!is_admin()) {

    $excluded = array( ‘5’ );

    set_query_var( ‘category__not_in’, $excluded );


    Maybe there is a better way to do this but it worked for me!


    • Tom

      Nice catch – thanks for leaving the note here.

  11. Caraspot

    Is there a simple way without plugin and it work to all page of wordpress. I have try to use all recommendation from some forums but it sitl not work

    • Tom

      You’d have to modify the theme and the code responsible for using The Loop in order to handle this. Using a plugin for it isn’t all bad (permitting the UI for it isn’t bad), but otherwise you’ll have to modify template code to handle it.

  12. manfaat akar

    it worked. Thank you!


    This is really helpfull..

    Thanks Brooo

  14. rootrrotan

    it worked. Thank you!

    • Tom

      Sure thing — glad to help!


    its work, very nice your post sir, helpfully thank you

  16. Biodata Artis

    Thanks Tom, this was really helpfull and timely for me.

  17. Terus Unik

    I thinks wordpress hard and me found this tips and very help me

  18. Apk paok

    its work, very nice your post sir, helpfully thank you

  19. Teknodiary

    This tips work for me,,thanks

  20. Siagian

    it worked. Thank you!

Leave a Reply

© 2020 Tom McFarlin

Theme by Anders NorenUp ↑