Classic theme loop template parts
Skill Lonsdale201/wp-agent-skills/theme-development/classic-theme-loop-template-parts
A community-maintained collection of agent skills for WordPress plugin and theme development.
npx -y skills add Lonsdale201/wp-agent-skills --skill classic-theme-loop-template-partsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 21 stars21 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Build or audit classic theme loops and PHP template parts for WP 7.0 without block/FSE assumptions. Covers the main Loop with `have_posts()` and `the_post()`, archive/search/single/page content choices, `get_template_part()` with `$args`, `content-none.php`, `post_class()`, secondary `WP_Query` loops with `wp_reset_postdata()`, `rewind_posts()`, pagination, single post navigation, and common bugs such as `query_posts()`, nested loops without reset, raw globals in template parts, and missing no-results states.
SKILL.md
7.9 KB, as published. Nobody here has run it
Classic Theme Loop and Template Parts
Use this when creating or reviewing the content rendering layer of a classic PHP WordPress theme: main loops, content.php style partials, archive cards, single post bodies, page bodies, empty states, and pagination.
This is not a block/FSE template-part skill. Use PHP template files and get_template_part().
When to Use This Skill
- Writing
index.php,home.php,archive.php,search.php,single.php,page.php, or404.php. - Moving repeated markup into
template-parts/content*.php. - Reviewing a nested
WP_Query, featured-post section, related posts section, or custom loop. - Fixing pagination, no-results output, post classes, content/excerpt choice, or "wrong global post" bugs.
Main Loop Contract
For the main query, use WordPress' loop state. Do not replace it with query_posts().
<?php
get_header();
?>
<main id="main" class="site-main">
<?php
if ( have_posts() ) :
while ( have_posts() ) :
the_post();
get_template_part( 'template-parts/content', get_post_type() );
endwhile;
the_posts_pagination();
else :
get_template_part( 'template-parts/content', 'none' );
endif;
?>
</main>
<?php
get_footer();
Rules:
- Call
the_post()inside thewhile ( have_posts() )loop before template tags that depend on the global post. - Include a no-results state.
template-parts/content-none.phpis the standard pattern. - Keep the loop in template files; move per-post markup into template parts.
- Do not call
query_posts()in themes. It mutates the main query and breaks pagination and conditionals. - Do not manually set global
$postunless a core API requires it and you fully restore state afterward.
Template Part Pattern
Use get_template_part() for repeated per-post markup:
get_template_part(
'template-parts/content',
get_post_type(),
array(
'heading_level' => is_singular() ? 'h1' : 'h2',
)
);
get_template_part( $slug, $name, $args ) searches for:
{$slug}-{$name}.php{$slug}.php
Inside the template part, $args is available:
<?php
$heading_level = isset( $args['heading_level'] ) ? $args['heading_level'] : 'h2';
$allowed_headings = array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' );
$heading_level = in_array( $heading_level, $allowed_headings, true ) ? $heading_level : 'h2';
?>
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
<header class="entry-header">
<?php the_title( '<' . $heading_level . ' class="entry-title">', '</' . $heading_level . '>' ); ?>
</header>
<div class="entry-content">
<?php the_excerpt(); ?>
</div>
</article>
Keep template parts presentation-focused. If a template part needs a lot of derived data, compute it before the call and pass a small $args array.
Archive vs Singular Output
Choose output by context:
- Archives, search results, and post grids usually use
the_excerpt()or a controlled custom summary. single.phpandpage.phpusually usethe_content().- After
the_content()in singular views, callwp_link_pages()so paginated posts render all pages. - Use
the_post_navigation()for previous/next post links on single posts when the design needs it. - Use
the_posts_pagination()for archive/search/blog pagination.
Example singular body:
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
<header class="entry-header">
<?php the_title( '<h1 class="entry-title">', '</h1>' ); ?>
</header>
<div class="entry-content">
<?php
the_content();
wp_link_pages(
array(
'before' => '<nav class="page-links" aria-label="' . esc_attr__( 'Post pages', 'textdomain' ) . '">',
'after' => '</nav>',
)
);
?>
</div>
</article>
Secondary Queries
For related posts, featured cards, or sidebar sections, use a separate WP_Query and always reset post data afterward.
$featured = new WP_Query(
array(
'post_type' => 'post',
'posts_per_page' => 3,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
)
);
if ( $featured->have_posts() ) :
while ( $featured->have_posts() ) :
$featured->the_post();
get_template_part( 'template-parts/card', get_post_type() );
endwhile;
endif;
wp_reset_postdata();
Rules:
- Use
wp_reset_postdata()after any customWP_Queryloop that calls$query->the_post(). - Use
no_found_rows => truewhen pagination is not needed. - Use
ignore_sticky_posts => truefor most curated secondary lists. - If you only need IDs, request IDs and render intentionally; do not run full post setup by habit.
- Do not call
wp_reset_query()unless you intentionally replaced the global query, which a theme should normally avoid.
Rewinding the Main Loop
If you read posts from the main query once and need to render the same main query again, call rewind_posts().
if ( have_posts() ) {
the_post();
// Inspect the first post.
}
rewind_posts();
while ( have_posts() ) {
the_post();
get_template_part( 'template-parts/content', get_post_type() );
}
Use this sparingly. If the first pass is only for layout decisions, prefer deriving the decision from query context instead of consuming loop state.
Required Theme Markup Hooks
Inside loop template parts:
- Use
post_class()on the root post element. - Use
the_ID()for stable post element IDs. - Use
the_title()with explicit wrappers rather than echoing raw title data. - Use
the_content(),the_excerpt(), and thumbnail/template tags rather than rebuilding core output with raw fields. - Escape any custom meta, options, term fields, or request-derived values before output.
Review Checklist
- Main query uses
have_posts()andthe_post(). - Empty results call a real no-results template.
- Archive/search pages do not dump full post content unless that is intentional.
- Singular templates use
the_content()and handlewp_link_pages(). - Pagination uses
the_posts_pagination()or a deliberate equivalent. - Secondary
WP_Queryloops callwp_reset_postdata(). - No
query_posts(). - Template parts receive explicit
$argswhen they need extra data. post_class()is preserved on post wrappers.- No raw custom fields/options are echoed inside template parts.
Common Mistakes
- Calling
the_post()before checkinghave_posts()and then wondering why the first post disappears. - Creating
content.phpbut forgettingcontent-none.php. - Using
get_template_part()for business logic instead of presentation. - Passing unescaped HTML through
$argsand echoing it blindly. - Nesting a custom query inside the main loop without restoring post data.
- Building archive pagination from
$_GET['paged']manually instead of using WordPress query state.
References
- Official documentation: https://developer.wordpress.org/themes/classic-themes/basics/the-loop/
- Official documentation: https://developer.wordpress.org/themes/classic-themes/basics/template-tags/
- Verified source paths:
wp-includes/query.phpwp-includes/class-wp-query.phpwp-includes/template.phpwp-includes/post-template.phpwp-includes/link-template.phpwp-content/themes/storefront/wp-content/themes/generatepress/