Excluded

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

query_posts

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.

Category:
Notes
Tags:

Join the conversation! 31 Comments

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

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

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

    • 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.

    • 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. 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. 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

  6. 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. 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. ^^

  8. Thanks, this is much smoother way.

  9. 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?

  10. 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!

    Thanks

  11. 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

    • 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. This is really helpfull..

    Thanks Brooo

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

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

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

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

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.