In some cases you might need to modify the global post query WordPress is performing on any page you are visiting – both admin and frontend. In this guide we will look at which hook to use, and how to change the query arguments to your liking.

Which hook to use

First you need to know which hook to add your code to. We need a hook that happens right after WordPress has set up all arguments, but before the actual query is run. For this we use the action pre_get_posts.

In this hook you get one parameter; which is a WP_Query object that WordPress will later run the query with. You make changes to the object – but you don’t need to return it as WordPress will run the query with the modified object.

add_action('pre_get_posts', function($query) {
	// Add your code here
});

Modifying the query most likely requires you to use some conditional tags to specify those cases you want to change it. For example you might want to target the query only in search results, or category views.

Conditional tags

WordPress has a bunch of conditional tags that you can use to specify which cases you want to add your code. A conditional tag is simply a function that returns true or false depending on which state WordPress is in. Examples of common conditional tags are is_admin() for checking if we currently are in admin or frontend, is_singular() if we are at a single post or page in frontend, and is_search() if we are at the search results page.

Keep in mind that the hook pre_get_posts is run both for admin and frontend. If you only wish to affect the global query in frontend, you need to wrap your code inside an if-check on the conditional tag is_admin().

Note about conditional tags in pre_get_posts

Conditional tags are great and all, but there are some things to keep in mind when using these inside pre_get_posts.

Firstly, you need to get familiar with the tag is_main_query(). The action pre_get_posts is actually run multiple times for each page load. For example pre_get_posts is run when generating each menu (including those in widgets). In order to modify the actual global query, e.g. posts for a category archive or search results, you need to target the “main query” using is_main_query().

Secondly, you need to be aware of cases where you need to check the conditional tags onto the provided object instead of calling the function “independently”. Usually when you use conditional tags, you would write it like this:

if (is_main_query()) {
	// Do stuff
}

However when using pre_get_posts, there are some conditional tags you need to apply onto the object. For example:

add_action('pre_get_posts', function($query) {
	if ($query->is_main_query()) {
		// Add your code here
	}
});

You should always check is_main_query() onto the object provided in pre_get_posts. Read the documentation for pre_get_posts for more information.

This is an example of checking if we are not in admin, and if we are at the main post query:

add_action('pre_get_posts', function($query) {
	if (!is_admin() && $query->is_main_query()) {
		// Add your code here
	}
});

Changing or adding arguments

Since we are working with a WP_Query object, you can refer to WP_Query’s documentation for how to build your arguments to customize the posts query. Keep in mind that arguments are already populated. In that case you need to append or change existing values. Or remove the ones you want to remove.

You use the set() function onto the WP_Query object to set arguments. The method accepts two arguments, the argument key and secondly the value. For example, setting the posts_per_page argument would be done like this:

add_action('pre_get_posts', function($query) {
	if (!is_admin() && $query->is_main_query()) {
		$query->set('posts_per_page', 20);
	}
});

In cases where you want to append or change a pre-existing argument, you would usually do this by first saving the existing argument in a variable. You can use the method get() for this. Then you modify the variable, by appending or merging arrays or whatnot. And finally you use set() to replace the modified variable back onto the query object. I recommend using var_dump() on the object to see what it contains, and this is also a nice way to check if your conditional tags are correct.

Here is a quick example of using get() (simply checking if it is empty), and if so, add your own arguments with set().

add_action('pre_get_posts', function($query) {
	if (!is_admin() && $query->is_main_query()) {
		if (!$query->get('meta_query')) {
			$query->set('meta_query', [/* your arguments here */]);
		}
	}
});

Note about tax_query

WordPress has separate subclasses for handling the taxonomy (WP_Tax_Query) part within WP_Query. If you need to do more complex taxonomy query modifications, the pre_get_posts action might be too early. Some values might be empty because they are populated later. In this case you might be better off using the action parse_tax_query instead of pre_get_posts. Read the documentation for this hook to see if this is for you.

Conclusion

The arguments you add or change depends entirely on what you want to do, but you should now have some insight in strategies to get ahold of the global query. If you need to get some insight into the WP_Query object and how to use its arguments, my post about how to query posts might be of interest.