D
P
0

WordPress & PHP

Your PHP Filter Runs but the Page Is Empty? A Theme Missing `page.php`/`search.php` Silently Falls to `index.php`

June 24, 2026·4 min read
Your PHP Filter Runs but the Page Is Empty? A Theme Missing `page.php`/`search.php` Silently Falls to `index.php`

The symptom was maddening: a search results page on a custom theme I built rendered nothing but an empty placeholder. No fatal error, no warning in the log, no nothing. Just a hollow content area. The archive page did the exact same thing. And this was after I'd wired up pre_get_posts to reshape the query, with an error_log inside the hook that printed cleanly every single time. My filter ran. The screen stayed blank.

So I started from the worst assumption: my filter must be broken.

add_action( 'pre_get_posts', function ( $query ) {
	if ( ! is_admin() && $query->is_main_query() && $query->is_search() ) {
		$query->set( 'post_type', 'listing' );
		$query->set( 'posts_per_page', 12 );
		error_log( 'pre_get_posts RAN for search: ' . $query->get( 's' ) );
	}
} );

The log fired on every search. The query args went in. The resulting WP_Query, when I dumped it, was correct — it had posts, found_posts was greater than zero. But the browser stayed empty. I burned hours tweaking the is_search() condition, second-guessing hook priority, suspecting some other plugin was clobbering my query. Every lead was a dead end, because I was debugging the one part that was already right.

Why this happens

I finally stopped staring at the filter and asked the question I should have asked first: which template file is WordPress actually loading for this page?

I opened the theme root and really looked at the listing. There was no search.php. No archive.php. No page.php. The theme had index.php, header.php, footer.php, and functions.php. And that index.php? A bare placeholder — wrapper markup, no real Loop.

That was the whole story. WordPress has a template hierarchy: for search results it looks for search.php first, falls back to archive.php if that's missing, then to index.php as the absolute last resort. The hierarchy never errors when a file is absent — it silently drops to the next level. Because my theme had neither search.php nor archive.php, every search and every archive was being served by index.php.

And here's the subtle trap: my hook still ran. pre_get_posts attaches to the query process, not to the template file. So my main query was modified correctly, the posts were fetched correctly, found_posts was correct — and then WordPress handed that result to index.php, which had no loop to render it. The data existed but was never printed. That's how "the filter runs but the page is empty" can both be true at once: they live on two different pipelines.

The fix

The decisive diagnostic wasn't another var_dump — it was confirming which file actually loaded. The fastest way: hook template_include and log the path.

add_filter( 'template_include', function ( $template ) {
	error_log( 'Template loaded: ' . $template );
	return $template;
}, 999 );

One search, and the log named it: .../themes/custom-theme/index.php. Not the search.php I'd been imagining. (If you'd rather not write the hook, Query Monitor shows the active template in its "Template" panel — same information.) Before blaming any filter, my first move now is to ls the theme root for the template file I expect, then confirm which one actually loaded.

The fix was obvious once the root cause was visible: add the missing template files, each with a real loop. Here's search.php:

<?php get_header(); ?>
 
<main class="search-results">
	<?php if ( have_posts() ) : ?>
		<h1>Results for "<?php echo esc_html( get_search_query() ); ?>"</h1>
		<?php while ( have_posts() ) : the_post(); ?>
			<article>
				<h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
				<?php the_excerpt(); ?>
			</article>
		<?php endwhile; ?>
	<?php else : ?>
		<p>No matching results.</p>
	<?php endif; ?>
</main>
 
<?php get_footer(); ?>

I added archive.php and page.php following the same pattern. Refresh — search results appeared immediately, all populated with the data my filter had been preparing the whole time. Not one line of pre_get_posts had to change. It was never wrong.

The takeaway

"My filter runs but nothing shows" frequently isn't about the filter at all — it's a signal that the template hierarchy resolved to a different file than you think. Hooks operate on the query pipeline; rendering operates on the template pipeline. Both can be right or wrong independently.

So before you add one more var_dump to your query logic, confirm the loaded template first. ls the theme root for the template you expect to exist, and hook template_include (or open Query Monitor) to see which one actually loads. A theme missing page.php, search.php, or archive.php won't complain — it'll quietly fall to index.php, and if your index.php is just a placeholder, you'll spend an afternoon debugging code that was correct all along.