Technote

Themes & plugins Practical

Series Child theme development in practice Part 6 of 8

Secondary queries in templates, and what forgetting the reset does

You add a related-posts list and everything below it starts pointing at the wrong post. It looks like a template bug; it is one missing line.

You add three related posts to a single-post template. Suddenly the comment area below shows another post’s discussion and the breadcrumb points somewhere else entirely. Stare at the template all you like — nothing there is wrong. The cause is a missing line.

the_post() mutates global state

WordPress template tags such as the_title(), the_permalink() and get_the_ID() take no arguments. They read the global $post instead. And what the_post() does inside a loop is precisely to point that global at the next item.

Run a secondary query and its the_post() touches the very same global. When the loop ends, the global is left pointing at the last item of the sub-loop. Unless you restore it, everything below renders the wrong post — and it presents itself as “the template is broken”.

Where the global $post is left without a reset — the code below cannot see the cause

wp_reset_postdata() restores the global to the main query’s current post. Put it immediately after the sub-loop, as a matched pair you always type together.

<?php
$related = new WP_Query( [
    'post_type'      => 'post',
    'posts_per_page' => 3,
    'post__not_in'   => [ get_the_ID() ],
    'no_found_rows'  => true,
] );

if ( $related->have_posts() ) :
    while ( $related->have_posts() ) :
        $related->the_post();
        get_template_part( 'template-parts/card', 'note' );
    endwhile;

    wp_reset_postdata();   // without this line, everything below drifts
endif;

Do not use query_posts()

There is a similar-looking function to avoid. query_posts() does not create a secondary query — it replaces the main one. Pagination breaks, conditional tags like is_single() start lying, and the database is queried an extra time for nothing.

To change which posts a listing shows, the answer is pre_get_posts. It modifies the main query before it runs, so there is no extra query and the conditional tags stay honest. The guard is mandatory.

<?php
add_action( 'pre_get_posts', function ( $query ) {
    if ( is_admin() || ! $query->is_main_query() ) {
        return;   // without this, admin lists and sub-queries change too
    }

    if ( $query->is_category() ) {
        $query->set( 'posts_per_page', 12 );
    }
} );

Arguments that keep a secondary query cheap

A secondary query runs on every render, so leaving the defaults means paying for work you do not use. Switch off what you do not need.

Secondary query review — switch the top four on, keep the bottom three out

posts_per_page => -1 is the dangerous one. During development there are ten posts and it behaves perfectly; it only slows down in production, once content has accumulated. Setting a ceiling makes the worst case predictable.

For the same reason, avoid querying inside a loop. One extra query per item means the cost multiplies with the length of the list rather than adding to it.

More on queries and performance sits in the Performance archive, and the order in which we narrow down causes on a real site is published on our process page.

Next part

That was the building half of the series. The remaining two parts are about taking things away. Next: removing parent theme assets you do not use, and the responsibility that comes with it.

More on this topic

All technotes

Themes & plugins Practical

Three reasons old content breaks in a new design

Inline styles baked into the body, shortcode remnants from plugins that are gone, hard-coded widths. These are where a new design system stops being honoured.

Designers 7 min read

Themes & plugins Practical

Decide the editable regions before you design them

A design the CMS cannot express stays up for negotiation long after it ships. Deciding what editors may change, first, removes the negotiation entirely.

Designers 6 min read

₩270,000 · Join the program