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 Practical

Turning taste arguments into rule checks

"It feels a bit cramped" can be neither argued with nor fixed. Spacing off the scale, colour off the palette, contrast below threshold, missing states — four rules…

Designers 9 min read

Development workflow Practical

Do not swap everything at once

A full swap makes every problem appear at the same moment — which means none of them can be attributed. So you switch one template at a time.

Designers 6 min read

Development workflow Practical

Adding an SCSS build, and whether to commit the output

WordPress themes are expected to deploy without a build step, which leads to the opposite conclusion from ordinary application code — and to its own costs.

Developers 7 min read

₩270,000 · Join the program