Claude Skill

classic-template-hierarchy

Choose, create, or audit classic PHP WordPress template files for WP 7.1 using the template hierarchy. Covers `template-loader.php`, `index.php` fallback, `front-page.php` vs `home.php`, page/single/archive/taxonomy/search/404/attachment templates, `get_template_part()` with `$ar

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download lonsdale201-wp-agent-skills-theme-development_classic-template-hierarchy-52f6020.zip · 3 KB
Part of lonsdale201/wp-agent-skills — 226 skills

Install

skills CLI npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/theme-development/classic-template-hierarchy
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install lonsdale201-wp-agent-skills@llmmart
Git git clone https://github.com/Lonsdale201/wp-agent-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole lonsdale201/wp-agent-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Classic Template Hierarchy

Use this when deciding which PHP template file a classic theme should contain, or when auditing why WordPress loads the wrong template.

This is a classic PHP theme skill. Do not apply FSE/block-template assumptions such as templates/page.html.

When to Use This Skill

  • Adding page.php, single.php, archive.php, 404.php, or similar template files.
  • Debugging front page vs blog index behavior.
  • Converting static pages into WordPress templates.
  • Creating custom page templates.
  • Reviewing template_include, template_redirect, get_template_part, or child-theme override behavior.

Loader Model in WP 7.1

wp-includes/template-loader.php checks query conditionals, asks a get_*_template() function for candidates, then falls back to index.php.

Important WP 7.1 details:

  • The final path passes through template_include.
  • WordPress resolves the returned template through realpath().
  • The final included template must be a readable .php or .html file.
  • wp_before_include_template fires immediately before inclusion.

Do not include a template and call exit from template_redirect. Core's own docblock says template loading should be changed via template_include so later hooks still run.

WordPress 7.1 adds WP_Query::$is_sitemap, WP_Query::is_sitemap(), and the global is_sitemap() conditional for requests with the sitemap query var. Like other conditional tags, call it only after the main query is parsed. It does not add a sitemap.php theme hierarchy entry. Core or an SEO plugin normally handles sitemap output during routing/hooks; do not render an XML sitemap through an HTML theme template merely because the conditional exists.

Child Theme Lookup

locate_template() searches:

  1. Active stylesheet directory, usually child theme.
  2. Parent template directory, when a child theme is active.
  3. wp-includes/theme-compat/ fallback for a few legacy files.

This means a child theme can override a parent template by adding the same relative file path.

Main Template Files

Request Preferred classic files
Site front page front-page.php, then the matching page/home flow, then index.php
Blog posts index home.php, then index.php
Static page custom page template, page-{slug}.php, page-{id}.php, page.php, singular.php, index.php
Single post/CPT custom post template, single-{post_type}-{slug}.php, single-{post_type}.php, single.php, singular.php, index.php
CPT archive archive-{post_type}.php, archive.php, index.php
Generic archive archive.php, index.php
Category category-{slug}.php, category-{id}.php, category.php, archive.php, index.php
Tag tag-{slug}.php, tag-{id}.php, tag.php, archive.php, index.php
Custom taxonomy taxonomy-{taxonomy}-{term}.php, taxonomy-{taxonomy}-{term_id}.php, taxonomy-{taxonomy}.php, taxonomy.php, archive.php, index.php
Author author-{nicename}.php, author-{id}.php, author.php, archive.php, index.php
Date date.php, archive.php, index.php
Search search.php, index.php
404 404.php, index.php
Attachment {mime_type}-{sub_type}.php, {sub_type}.php, {mime_type}.php, attachment.php, singular.php, index.php

front-page.php is special: it wins for the site front page whether the front page is a static page or the posts index.

home.php is the blog posts index, not the homepage in every configuration.

Template Responsibilities

File Responsibility
index.php Final fallback. Should render a valid loop and no-results state.
header.php Doctype, <html>, <head>, wp_head(), opening <body>, wp_body_open(), site header.
footer.php Site footer, wp_footer(), closing body/html.
front-page.php Bespoke front page layout.
home.php Blog posts index.
page.php Static WordPress pages. Not posts, not archives.
single.php Single posts and CPTs when no more specific single template exists.
singular.php Shared fallback for pages/posts/attachments before index.php.
archive.php Shared fallback for taxonomy/date/author/post type archives.
search.php Search results page.
404.php Not-found response view; include search/navigation help.
comments.php Comment list/form markup loaded by comments_template().
searchform.php Custom search form loaded by get_search_form().

Thin Template Pattern

Keep top-level templates small. Let template parts carry repeated post markup.

<?php
/**
 * Main fallback template.
 *
 * @package MyTheme
 */

get_header();
?>

<main id="primary" class="site-main">
    <?php if ( have_posts() ) : ?>
        <?php
        while ( have_posts() ) :
            the_post();

            get_template_part(
                'template-parts/content',
                get_post_type(),
                array(
                    'show_excerpt' => is_archive() || is_search(),
                )
            );
        endwhile;

        the_posts_pagination();
        ?>
    <?php else : ?>
        <?php get_template_part( 'template-parts/content', 'none' ); ?>
    <?php endif; ?>
</main>

<?php
get_footer();

get_template_part( $slug, $name, $args ) searches {$slug}-{$name}.php, then {$slug}.php, and passes $args into the template. Use this instead of setting temporary globals for template parts.

Custom Page Templates

WordPress scans PHP files in the theme root and one directory deep for Template Name. In WordPress 7.1, WP_Theme::get_post_templates() parses Template Name and Template Post Type through get_file_data(). Keep both headers within the first 8 KB of the file and use normal header-comment syntax; do not generate them dynamically or place a large preamble before them.

<?php
/**
 * Template Name: Landing Page
 * Template Post Type: page
 *
 * @package MyTheme
 */

get_header();

while ( have_posts() ) :
    the_post();
    get_template_part( 'template-parts/content', 'landing' );
endwhile;

get_footer();

Put these in page-templates/ when there are several. Do not create page-about.php for editor-selectable layouts; use a custom page template header.

Safe Template Overrides

To override the selected template globally or conditionally:

add_filter( 'template_include', 'mytheme_template_include' );

function mytheme_template_include( string $template ): string {
    if ( ! is_singular( 'event' ) ) {
        return $template;
    }

    $event_template = locate_template( array( 'single-event.php' ) );

    return $event_template ?: $template;
}

Never build a template path directly from $_GET, $_POST, route segments, or unvalidated meta. Use fixed candidate lists and locate_template().

Required Header/Footer Hooks

header.php:

<!doctype html>
<html <?php language_attributes(); ?>>
<head>
    <meta charset="<?php bloginfo( 'charset' ); ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>

footer.php:

<?php wp_footer(); ?>
</body>
</html>

Missing wp_head() or wp_footer() breaks core, plugins, admin bar assets, and enqueued scripts/styles.

Common Mistakes

  • Treating home.php as the marketing homepage. It is the blog posts index.
  • Putting all routes into page.php; posts, archives, search, and 404 use other hierarchy branches.
  • Creating one-off page-{slug}.php files when the editor needs a reusable page template.
  • Directly include-ing template files instead of using get_header(), get_footer(), comments_template(), or get_template_part().
  • Using template_redirect to include a file and exit.
  • Returning request-controlled paths from template_include.
  • Forgetting that child theme files override parent files by relative path.
  • Inventing a sitemap.php theme template because WordPress 7.1 added is_sitemap(); no such hierarchy branch exists.

Cross-References

  • Theme bootstrapping, folders, assets: classic-theme-structure
  • Broader WP security review: wp-security-audit

References

Files (wp-agent-skills)
  • SKILL.md 9.6 KB
    ---
    name: classic-template-hierarchy
    description: Choose, create, or audit classic PHP WordPress template files for WP 7.1 using the template hierarchy. Covers `template-loader.php`, `index.php` fallback, `front-page.php` vs `home.php`, page/single/archive/taxonomy/search/404/attachment templates, `get_template_part()` with `$args`, child-theme override order, `template_include`, the `is_sitemap()` conditional, and why `template_redirect` should not include-and-exit. Use when deciding which classic template file to add or reviewing page.php/404.php/single/archive behavior.
    metadata:
      wp-skills-author: "Soczó Kristóf"
      wp-skills-contact: "mailto:lonsdale201@hotmail.com"
      wp-skills-plugin: "wordpress"
      wp-skills-plugin-version-tested: "7.1"
      wp-skills-wp-version-tested: "7.1"
      wp-skills-php-min: "7.4"
      wp-skills-last-updated: "2026-08-20"
    ---
    
    # Classic Template Hierarchy
    
    Use this when deciding which PHP template file a classic theme should contain, or when auditing why WordPress loads the wrong template.
    
    This is a classic PHP theme skill. Do not apply FSE/block-template assumptions such as `templates/page.html`.
    
    ## When to Use This Skill
    
    - Adding `page.php`, `single.php`, `archive.php`, `404.php`, or similar template files.
    - Debugging front page vs blog index behavior.
    - Converting static pages into WordPress templates.
    - Creating custom page templates.
    - Reviewing `template_include`, `template_redirect`, `get_template_part`, or child-theme override behavior.
    
    ## Loader Model in WP 7.1
    
    `wp-includes/template-loader.php` checks query conditionals, asks a `get_*_template()` function for candidates, then falls back to `index.php`.
    
    Important WP 7.1 details:
    
    - The final path passes through `template_include`.
    - WordPress resolves the returned template through `realpath()`.
    - The final included template must be a readable `.php` or `.html` file.
    - `wp_before_include_template` fires immediately before inclusion.
    
    Do not include a template and call `exit` from `template_redirect`. Core's own docblock says template loading should be changed via `template_include` so later hooks still run.
    
    WordPress 7.1 adds `WP_Query::$is_sitemap`, `WP_Query::is_sitemap()`, and the
    global `is_sitemap()` conditional for requests with the `sitemap` query var.
    Like other conditional tags, call it only after the main query is parsed.
    It does **not** add a `sitemap.php` theme hierarchy entry. Core or an SEO plugin
    normally handles sitemap output during routing/hooks; do not render an XML
    sitemap through an HTML theme template merely because the conditional exists.
    
    ## Child Theme Lookup
    
    `locate_template()` searches:
    
    1. Active stylesheet directory, usually child theme.
    2. Parent template directory, when a child theme is active.
    3. `wp-includes/theme-compat/` fallback for a few legacy files.
    
    This means a child theme can override a parent template by adding the same relative file path.
    
    ## Main Template Files
    
    | Request | Preferred classic files |
    |---|---|
    | Site front page | `front-page.php`, then the matching page/home flow, then `index.php` |
    | Blog posts index | `home.php`, then `index.php` |
    | Static page | custom page template, `page-{slug}.php`, `page-{id}.php`, `page.php`, `singular.php`, `index.php` |
    | Single post/CPT | custom post template, `single-{post_type}-{slug}.php`, `single-{post_type}.php`, `single.php`, `singular.php`, `index.php` |
    | CPT archive | `archive-{post_type}.php`, `archive.php`, `index.php` |
    | Generic archive | `archive.php`, `index.php` |
    | Category | `category-{slug}.php`, `category-{id}.php`, `category.php`, `archive.php`, `index.php` |
    | Tag | `tag-{slug}.php`, `tag-{id}.php`, `tag.php`, `archive.php`, `index.php` |
    | Custom taxonomy | `taxonomy-{taxonomy}-{term}.php`, `taxonomy-{taxonomy}-{term_id}.php`, `taxonomy-{taxonomy}.php`, `taxonomy.php`, `archive.php`, `index.php` |
    | Author | `author-{nicename}.php`, `author-{id}.php`, `author.php`, `archive.php`, `index.php` |
    | Date | `date.php`, `archive.php`, `index.php` |
    | Search | `search.php`, `index.php` |
    | 404 | `404.php`, `index.php` |
    | Attachment | `{mime_type}-{sub_type}.php`, `{sub_type}.php`, `{mime_type}.php`, `attachment.php`, `singular.php`, `index.php` |
    
    `front-page.php` is special: it wins for the site front page whether the front page is a static page or the posts index.
    
    `home.php` is the blog posts index, not the homepage in every configuration.
    
    ## Template Responsibilities
    
    | File | Responsibility |
    |---|---|
    | `index.php` | Final fallback. Should render a valid loop and no-results state. |
    | `header.php` | Doctype, `<html>`, `<head>`, `wp_head()`, opening `<body>`, `wp_body_open()`, site header. |
    | `footer.php` | Site footer, `wp_footer()`, closing body/html. |
    | `front-page.php` | Bespoke front page layout. |
    | `home.php` | Blog posts index. |
    | `page.php` | Static WordPress pages. Not posts, not archives. |
    | `single.php` | Single posts and CPTs when no more specific single template exists. |
    | `singular.php` | Shared fallback for pages/posts/attachments before `index.php`. |
    | `archive.php` | Shared fallback for taxonomy/date/author/post type archives. |
    | `search.php` | Search results page. |
    | `404.php` | Not-found response view; include search/navigation help. |
    | `comments.php` | Comment list/form markup loaded by `comments_template()`. |
    | `searchform.php` | Custom search form loaded by `get_search_form()`. |
    
    ## Thin Template Pattern
    
    Keep top-level templates small. Let template parts carry repeated post markup.
    
    ```php
    <?php
    /**
     * Main fallback template.
     *
     * @package MyTheme
     */
    
    get_header();
    ?>
    
    <main id="primary" class="site-main">
        <?php if ( have_posts() ) : ?>
            <?php
            while ( have_posts() ) :
                the_post();
    
                get_template_part(
                    'template-parts/content',
                    get_post_type(),
                    array(
                        'show_excerpt' => is_archive() || is_search(),
                    )
                );
            endwhile;
    
            the_posts_pagination();
            ?>
        <?php else : ?>
            <?php get_template_part( 'template-parts/content', 'none' ); ?>
        <?php endif; ?>
    </main>
    
    <?php
    get_footer();
    ```
    
    `get_template_part( $slug, $name, $args )` searches `{$slug}-{$name}.php`, then `{$slug}.php`, and passes `$args` into the template. Use this instead of setting temporary globals for template parts.
    
    ## Custom Page Templates
    
    WordPress scans PHP files in the theme root and one directory deep for `Template Name`.
    In WordPress 7.1, `WP_Theme::get_post_templates()` parses `Template Name` and
    `Template Post Type` through `get_file_data()`. Keep both headers within the
    first 8 KB of the file and use normal header-comment syntax; do not generate
    them dynamically or place a large preamble before them.
    
    ```php
    <?php
    /**
     * Template Name: Landing Page
     * Template Post Type: page
     *
     * @package MyTheme
     */
    
    get_header();
    
    while ( have_posts() ) :
        the_post();
        get_template_part( 'template-parts/content', 'landing' );
    endwhile;
    
    get_footer();
    ```
    
    Put these in `page-templates/` when there are several. Do not create `page-about.php` for editor-selectable layouts; use a custom page template header.
    
    ## Safe Template Overrides
    
    To override the selected template globally or conditionally:
    
    ```php
    add_filter( 'template_include', 'mytheme_template_include' );
    
    function mytheme_template_include( string $template ): string {
        if ( ! is_singular( 'event' ) ) {
            return $template;
        }
    
        $event_template = locate_template( array( 'single-event.php' ) );
    
        return $event_template ?: $template;
    }
    ```
    
    Never build a template path directly from `$_GET`, `$_POST`, route segments, or unvalidated meta. Use fixed candidate lists and `locate_template()`.
    
    ## Required Header/Footer Hooks
    
    `header.php`:
    
    ```php
    <!doctype html>
    <html <?php language_attributes(); ?>>
    <head>
        <meta charset="<?php bloginfo( 'charset' ); ?>">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <?php wp_head(); ?>
    </head>
    <body <?php body_class(); ?>>
    <?php wp_body_open(); ?>
    ```
    
    `footer.php`:
    
    ```php
    <?php wp_footer(); ?>
    </body>
    </html>
    ```
    
    Missing `wp_head()` or `wp_footer()` breaks core, plugins, admin bar assets, and enqueued scripts/styles.
    
    ## Common Mistakes
    
    - Treating `home.php` as the marketing homepage. It is the blog posts index.
    - Putting all routes into `page.php`; posts, archives, search, and 404 use other hierarchy branches.
    - Creating one-off `page-{slug}.php` files when the editor needs a reusable page template.
    - Directly `include`-ing template files instead of using `get_header()`, `get_footer()`, `comments_template()`, or `get_template_part()`.
    - Using `template_redirect` to include a file and `exit`.
    - Returning request-controlled paths from `template_include`.
    - Forgetting that child theme files override parent files by relative path.
    - Inventing a `sitemap.php` theme template because WordPress 7.1 added
      `is_sitemap()`; no such hierarchy branch exists.
    
    ## Cross-References
    
    - Theme bootstrapping, folders, assets: `classic-theme-structure`
    - Broader WP security review: `wp-security-audit`
    
    ## References
    
    - Official documentation: <https://developer.wordpress.org/themes/classic-themes/basics/template-hierarchy/>
    - Official documentation: <https://developer.wordpress.org/themes/classic-themes/basics/template-files/>
    - Official documentation: <https://developer.wordpress.org/themes/classic-themes/templates/page-template-files/>
    - Verified source paths:
      - `wp-includes/template-loader.php`
      - `wp-includes/template.php`
      - `wp-includes/general-template.php`
      - `wp-includes/class-wp-theme.php`
      - `wp-content/themes/storefront/index.php`
      - `wp-content/themes/storefront/page.php`
      - `wp-content/themes/storefront/404.php`
      - `wp-content/themes/generatepress/header.php`
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related