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.