Technote

Development workflow Practical

Series Using hooks and filters properly Part 4 of 8

pre_get_posts: miss two guard lines and the admin changes too

This hook fires for every WP_Query — menus, widgets and admin list tables included. Without guards, screens you never meant to touch change quietly.

pre_get_posts lets you edit a query’s arguments just before it runs — post counts on an archive, restricting to certain posts, changing the ordering. The important part is that you are modifying the main query rather than building a second one in the template, which keeps pagination and the conditional tags intact.

This hook fires for every query

The first thing to know is that it runs for every WP_Query instance, not just the main one. It fires while menus are built, while a widget fetches recent posts, and while the admin post list table is drawn.

What changes with you when there is no guard — the bottom four were never the target

Which is why the first two lines of the callback are effectively fixed.

add_action( 'pre_get_posts', 'wper_book_archive' );

function wper_book_archive( $query ) {
	if ( is_admin() || ! $query->is_main_query() ) {
		return;
	}

	if ( ! $query->is_post_type_archive( 'book' ) ) {
		return;
	}

	$query->set( 'posts_per_page', 12 );
	$query->set( 'orderby', 'menu_order' );
}

There is a reason to test with $query-> methods rather than the global conditional tags. The globals always describe the main query, so they can answer about something other than the query currently in your hands.

Read, modify, write — never overwrite

Calling set() on meta_query or tax_query outright discards whatever was already there. Another plugin may have set it; WordPress itself may have set it for that screen. Read it, add to it, put it back.

$meta = (array) $query->get( 'meta_query' );

$meta[] = [
	'key'     => '_in_stock',
	'value'   => '1',
	'compare' => '=',
];

$query->set( 'meta_query', $meta );

Somebody else’s condition can empty your archives

The failure runs in the other direction too. In this repository, ten custom taxonomy archives once stood at zero posts. The rewrites and the queried object were both correct, yet the executed SQL carried a _thumbnail_id join we had never asked for.

It came from the parent theme, which hides portfolio items without a featured image via pre_get_posts, deciding with $query->is_tax( $portfolio_taxonomies ). This site has no portfolio post type, so that array is empty — and is_tax() reads an empty first argument as “not restricted to any taxonomy” and returns true unconditionally. Every custom taxonomy archive became a portfolio query, and since our thumbnails are drawn in code rather than uploaded, nothing had a featured image and every list came back empty.

The pages returned 200, the headings and filters rendered correctly, and even our empty-state message printed properly — it reads as “no posts yet” rather than as a fault. Two lessons: never treat an empty array as equivalent to an empty string, and when a list looks wrong, inspect the query that actually ran rather than the arguments you meant to set.

Query debugging tools and the caveats around running them live in the development workflow archive, and a full sweep including issues of this kind is part of our optimization program.

Next part

Next comes the_content. The same filter hands you a completely different string at priority 1 than at priority 12, and most cases of “my regex never matches anything” start there.

More on this topic

All technotes

Development workflow Advanced

Running WP-CLI in CI: what the checks actually look like

Checks that rely on somebody remembering get skipped in a busy week. Stack the cheap ones first, run the rest against a disposable WordPress, and the remembering stops…

Developers 8 min read

Development workflow Advanced

Finding out what is attached to a hook, in order

A callback that never runs, one that runs and gets overwritten, and a hook that never fires are three different problems. Reading the registry separates all three at…

Developers 8 min read

₩270,000 · Join the program