How to create theme templates using the Template Hierarchy in WordPress

Contents

Overview

This tutorial explains, in exhaustive detail, how to create WordPress theme templates using the Template Hierarchy. It covers the hierarchy rules, file naming conventions, conditional tags, template parts, custom post types, child-theme overrides, debugging methods, and advanced techniques such as using filters and locate_template. Practical, copy-paste-ready examples are included and all code samples are placed in the required pre blocks.

What is the Template Hierarchy?

The Template Hierarchy is WordPresss system for selecting which theme file(s) to use to render a given request (a single post, a category archive, the homepage, a search result, etc.). WordPress checks a sequence of possible template files in a specific order and uses the first one that exists. If none of the more-specific files are present, WordPress falls back to more generic templates, ultimately falling back to index.php.

Core concepts and key functions

  • index.php — The ultimate fallback template required in every theme.
  • get_header(), get_footer(), get_sidebar() — Include header.php, footer.php, sidebar.php or their alternatives.
  • get_template_part() — Load reusable template parts (partials).
  • locate_template() — Find template files in parent/child themes programmatically.
  • template_include filter — Replace the final template file used to render a page.
  • Conditional Tags — Functions like is_single(), is_page(), is_archive(), is_front_page(), and is_home() are used to determine the query context.

Template Hierarchy — Quick reference lists (by request type)

Below are the ordered lists WordPress checks for various request types. Create the files in your theme root to target these contexts. If multiple names are provided they are checked in the order shown stop at the first file that exists.

1) Single post of a custom post type

  1. single-{post_type}-{slug}.php (rare)
  2. single-{post_type}.php
  3. single.php
  4. singular.php
  5. index.php

2) Single post (post post_type=post)

  1. single-{post_type}.php (i.e. single-post.php)
  2. single.php
  3. singular.php
  4. index.php

3) Page (static pages)

  1. custom page template (declared in file header with Template Name)
  2. page-{slug}.php
  3. page-{id}.php
  4. page.php
  5. singular.php
  6. index.php

4) Archive (post type archive)

  1. archive-{post_type}.php
  2. archive.php
  3. index.php

5) Category archive

  1. category-{slug}.php
  2. category-{id}.php
  3. category.php
  4. archive.php
  5. index.php

6) Tag archive

  1. tag-{slug}.php
  2. tag-{id}.php
  3. tag.php
  4. archive.php
  5. index.php

7) Taxonomy archive

  1. taxonomy-{taxonomy}-{term}.php
  2. taxonomy-{taxonomy}.php
  3. taxonomy.php
  4. archive.php
  5. index.php

8) Author archive

  1. author-{nicename}.php
  2. author-{id}.php
  3. author.php
  4. archive.php
  5. index.php

9) Date archive

  1. date.php
  2. archive.php
  3. index.php

10) Search results

  1. search.php
  2. index.php

11) 404 Not Found

  1. 404.php
  2. index.php

12) Attachment

  1. mime-type.php (e.g. image.php)
  2. attachment.php
  3. single.php
  4. index.php

13) Front page / Home interplay

  1. front-page.php — Highest priority when a static front page is set or always if front-page.php exists.
  2. home.php — Used for the posts page when a static front page is set used as the blog posts index otherwise if front-page.php is absent.
  3. index.php

Practical file examples

These examples show minimal, functional template files that you can use as a starting point. Each example is contained in a pre block so it can be copied directly.

index.php (fallback)

lt?php
/
  index.php
  Fallback template.
 /
get_header()
if ( have_posts() ) {
    while ( have_posts() ) {
        the_post()
        get_template_part( template-parts/content, get_post_type() )
    }
    the_posts_pagination()
} else {
    get_template_part( template-parts/content, none )
}
get_footer()
?gt

single.php (single post)

lt?php
/
  single.php
 /
get_header()
if ( have_posts() ) {
    while ( have_posts() ) {
        the_post()
        get_template_part( template-parts/content, single )
        comments_template()
    }
}
get_footer()
?gt

page.php (static pages)

lt?php
/
  page.php
 /
get_header()
while ( have_posts() ) {
    the_post()
    get_template_part( template-parts/content, page )
}
get_footer()
?gt

archive.php (generic archive)

lt?php
/
  archive.php
 /
get_header()
if ( have_posts() ) {
    the_archive_title( lth1gt, lt/h1gt )
    while ( have_posts() ) {
        the_post()
        get_template_part( template-parts/content, get_post_type() )
    }
    the_posts_pagination()
} else {
    get_template_part( template-parts/content, none )
}
get_footer()
?gt

template-parts/content-single.php (partial for single)

lt?php
/
  template-parts/content-single.php
 /
?gt
ltarticle id=post-lt?php the_ID() ?gt lt?php post_class() ?gtgt
    ltheader class=entry-headergt
        lth1 class=entry-titlegtlt?php the_title() ?gtlt/h1gt
    lt/headergt
    ltdiv class=entry-contentgt
        lt?php the_content() ?gt
    lt/divgt
lt/articlegt

page template header (custom template)

To create a custom page template you declare a header comment. This file can be named anything (commonly page-template-{name}.php) and will appear in the page editor Template dropdown.

lt?php
/
  Template Name: Two Column Landing
  Template Post Type: page
 /
get_header()
?gt
ltdiv class=two-column-layoutgt
    lt?php
    while ( have_posts() ) {
        the_post()
        the_title( lth1gt, lt/h1gt )
        the_content()
    }
    ?gt
lt/divgt
lt?php get_footer() ?gt

get_template_part() and template parts

Use get_template_part() to load smaller, reusable blocks of markup. This enforces a pattern of breaking templates into parts such as header, footer, content, and components. The function attempts to load slug-name.php first and then slug.php.

// Example: loads template-parts/content-single.php then template-parts/content.php
get_template_part( template-parts/content, single )

If you want to load from a subdirectory and provide fallback behavior, use locate_template() and load_template() for fine-grained control. For example:

templates = array(
    partials/card-special.php,
    partials/card.php
)
located = locate_template( templates, true, false ) // true = require it, false = not in theme root

Custom Post Types and templates

When registering custom post types, you can create templates named single-{post_type}.php and archive-{post_type}.php to control single and archive views. Ensure has_archive is set to true in the registration if you want archive pages.

// functions.php: register a custom post type with archive
function mytheme_register_book_cpt() {
    register_post_type( book, array(
        label => Books,
        public => true,
        has_archive => true,
        rewrite => array( slug => books ),
        supports => array( title, editor, thumbnail ),
    ) )
}
add_action( init, mytheme_register_book_cpt )

Then provide single-book.php and archive-book.php in your theme root to customize those templates.

Pages: page-{slug}.php vs page-{id}.php vs custom templates

  • page-{slug}.php — Useful when you want a template tied to a specific slug that will remain stable.
  • page-{id}.php — Useful when you want an id-specific template (less portable across environments because IDs differ).
  • Custom page templates — Add the Template Name header comment to let editors choose templates in the admin UI. Prefer these when you need editor control.

Front-page.php vs home.php

front-page.php overrides home.php when present. Use front-page.php for a custom static front page regardless of whether the front page is set to show latest posts or a static page. Use home.php for the blog posts index when front-page.php is not used or when a static front page is set and a separate posts page is defined.

Working with conditional tags

Conditional tags let you adjust behavior within templates or within functions to pick or route templates. Examples:

if ( is_singular( book ) ) {
    // do something specific for single book pages
}

if ( is_front_page() ) {
    // front page
}

if ( is_home()  ! is_front_page() ) {
    // blog posts index when not using front-page for posts
}

Overriding templates in child themes

Child themes override parent theme templates simply by providing files with the same names in the child theme directory. WordPress checks the child theme first, then the parent. Use get_template_part() and locate_template() in a child-safe manner.

// Example: locate_template checks child theme first, then parent
locate_template( array( template-parts/content-special.php ), true )

filtering the template selection

If you need to programmatically choose a template file, use the template_include filter or template_redirect actions. Use caution prefer standard hierarchy and naming where possible.

// Example: force a custom template for a specific post ID
add_filter( template_include, function( template ) {
    if ( is_single()  get_the_ID() === 123 ) {
        custom = locate_template( special-template.php )
        if ( custom ) {
            return custom
        }
    }
    return template
} )

Debugging which template is used

  • Enable debugging to see template-related notices in logs: set WP_DEBUG to true in wp-config.php.
  • Install Query Monitor plugin or the Show Current Template plugin to visualize which template is being used.
  • You can add temporary debug code to template-loader.php or to your theme header to echo current template: use get_page_template(), get_single_template(), or debug backtraces with locate_template() results for development only.
// Simple debug in a theme file (development only)
if ( defined( WP_DEBUG )  WP_DEBUG ) {
    global template
    echo lt!-- Current template:  . esc_html( template ) .  --gt
}

Template parts naming conventions and fallbacks

Use a consistent naming convention in template-parts subfolder. Examples:

  • template-parts/content.php — generic content display
  • template-parts/content-single.php — for single views
  • template-parts/content-excerpt.php — for archive excerpts
  • template-parts/header/site-branding.php — branding/hero

get_template_part( template-parts/content, get_post_type() ) will search for template-parts/content-{post_type}.php and fallback to template-parts/content.php automatically.

Comments templates

Use comments_template() to include comments. WordPress looks for comments.php in the theme root. You may place comments-related markup inside a template part and include it from single.php or page.php.

Full-Site Editing and Block Themes (note)

WordPress Full Site Editing (FSE) and block-based themes store templates and template parts as HTML files in the themes templates and parts folders (often created via a block theme). Those templates are different from classic PHP templates. If building a classic PHP theme, the Template Hierarchy described in this article applies. If building an FSE theme, follow the block-based templates structure and use theme.json, templates/.html, and parts/.html.

Best practices

  1. Start with a minimal set: index.php, style.css, functions.php, header.php, footer.php, and sidebar.php.
  2. Break content into template parts for reuse and readability.
  3. Prefer semantic and descriptive template names: archive-{post_type}.php and single-{post_type}.php.
  4. Use custom templates (Template Name header) when editors need to select templates from the UI.
  5. Avoid copying the entire parent theme into a child theme override only the files you need to change.
  6. Follow WordPress coding standards for PHP, HTML and escaping functions.
  7. Keep logic out of templates where possible—use functions.php and template tags to encapsulate logic.

Performance considerations

  • Minimize heavy queries in templates—move queries to functions and use proper caching (transients, object cache).
  • Reduce complexity in template parts that are loaded frequently (e.g., list items on archive pages).
  • Use get_template_part() rather than include() so child themes and filters are respected.

Advanced examples

Force a custom template for a custom taxonomy term

add_filter( template_include, function( template ) {
    if ( is_tax( genre, sci-fi ) ) {
        file = locate_template( array( taxonomy-genre-sci-fi.php, taxonomy-genre.php ) )
        if ( file ) {
            return file
        }
    }
    return template
} )

Provide a fallback partial path loader

function mytheme_get_partial( slug, name =  ) {
    templates = array()
    if ( name ) {
        templates[] = template-parts/{slug}-{name}.php
    }
    templates[] = template-parts/{slug}.php
    locate_template( templates, true, false )
}
mytheme_get_partial( card, large )

Checklist when creating templates

  1. Decide which contexts you need specific templates for (singular, archives, CPTs, pages).
  2. Create the most-specific template files you need (single-{post_type}.php, page-{slug}.php, archive-{post_type}.php, etc.).
  3. Build reusable template-parts and use get_template_part().
  4. Test child-theme overrides and plugin interactions.
  5. Enable WP_DEBUG to surface PHP/template errors while developing.
  6. Use Query Monitor or similar to confirm the exact template file used.

Further reading and resources

Summary

The WordPress Template Hierarchy is predictable and flexible. Name files according to the contexts described above, split markup into template-parts, and prefer hierarchy over programmatic overrides except when necessary. Use child themes to safely override parent themes, and use the debugging tips to confirm which template is being used. With these practices you can craft maintainable, performant themes that render precisely the contexts you need.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Leave a Reply

Your email address will not be published. Required fields are marked *