Skip to content
ExponaBLOG
WORDPRESS DEVELOPMENT & PLUGINS · DAY 030 / 100

The template hierarchy: a WordPress guide for developers

How WordPress picks a template.

Expona Admin5 min read

Understanding WordPress Template Selection

WordPress pages look different depending on what you're viewing—a single post looks different from an archive, and archives look different from custom post type pages. The system that decides which file to load is the template hierarchy, and understanding it is essential for building maintainable themes and plugins. We'll walk through how WordPress picks a template, why it matters for your architecture, and how to debug when things go sideways.

The Most Specific Template Wins

The core principle of the WordPress template hierarchy is this: the most specific template file wins. WordPress checks for templates in a strict order, from most specific to least specific, and uses the first one it finds. This cascading fallback system is elegant—if you don't have a specific template, WordPress keeps looking for broader ones until it finds something to display.

Consider a real scenario. You're building an e-commerce site with a custom post type called product. A visitor lands on the single product page for a coffee maker. WordPress checks in this order:

  1. single-product-{slug}.php (e.g., single-product-deluxe-coffee-maker.php)
  2. single-product.php
  3. single.php
  4. singular.php
  5. index.php

The moment it finds one, it stops searching. This means if you only have single.php, it'll use that for every single post type. If you create single-product.php, all product posts use it instead. If you somehow create single-product-deluxe-coffee-maker.php, only that exact post uses it.

In practice, most of us only need single-product.php. We rarely go down to the slug level unless we're handling something truly one-off.

Custom Post Types and single-{post-type}.php

Custom post types are where the template hierarchy becomes genuinely useful. Let's say you're building a team directory alongside your blog. You register a team_member post type.

php
register_post_type( 'team_member', array(
    'public'             => true,
    'has_archive'        => true,
    'rewrite'            => array( 'slug' => 'team' ),
    'supports'           => array( 'title', 'editor', 'thumbnail' ),
    'show_in_rest'       => true,
) );

Now when someone visits /team/alice-johnson/, WordPress looks for single-team_member.php first. If that doesn't exist, it falls back to single.php, then singular.php, then index.php.

The payoff: you can style your team member page completely differently from blog posts without touching your main single.php. Your team member template might show different meta information, a different sidebar, or custom fields. Your blog post template stays clean and focused.

php
// In single-team_member.php
get_header();
while ( have_posts() ) {
    the_post();
    echo '<h1>' . get_the_title() . '</h1>';
    echo '<div class="team-role">' . get_field( 'role' ) . '</div>';
    the_post_thumbnail( 'large' );
    the_content();
}
get_footer();

This separation of concerns makes your codebase easier to maintain. Team pages can evolve independently from blog post templates.

Archive and Taxonomy Templates

Archives (like /blog/ listing all posts) and taxonomy pages (like /category/tutorials/ or /tag/react/) also follow a hierarchy. Understanding this is critical when you're styling list pages differently.

For a category archive, WordPress checks:

  1. category-{slug}.php (e.g., category-tutorials.php)
  2. category-{id}.php (e.g., category-5.php)
  3. category.php
  4. archive.php
  5. index.php

For custom post type archives (say, /products/), it's:

  1. archive-product.php
  2. archive.php
  3. index.php

For custom taxonomies, the logic mirrors categories. A custom taxonomy called product_category with a term electronics checks:

  1. taxonomy-product_category-electronics.php
  2. taxonomy-product_category.php
  3. taxonomy.php
  4. archive.php
  5. index.php

Here's where many developers stumble: they forget to register the taxonomy with 'publicly_queryable' => true, so the archive never gets its own URL. Always double-check your taxonomy registration:

php
register_taxonomy( 'product_category', 'product', array(
    'public'            => true,
    'publicly_queryable' => true,  // Critical!
    'rewrite'           => array( 'slug' => 'product-category' ),
    'show_in_rest'      => true,
) );

Using Query Monitor to See the Chosen Template

Debugging which template is actually loading can be maddening without the right tool. Query Monitor is a free, indispensable WordPress plugin that shows you exactly which template file WordPress chose and why.

Install Query Monitor, enable it, and visit any page. In the Query Monitor panel (usually at the bottom right), look for the "Templates" section. It'll show you the full hierarchy that was checked and highlight which one was actually used in green.

This is invaluable when:

  • Your new template file isn't being picked up (maybe you named it wrong—filenames must match post type slugs exactly)
  • You're not sure why a page looks the way it does
  • You're inheriting a theme and need to trace how pages are styled
  • You want to verify that a custom post type archive is wired correctly

Beyond templates, Query Monitor also shows database queries, hooks being fired, and PHP errors. It's as essential as a browser's developer tools.

Common Mistakes

Mistake 1: Naming custom post type templates incorrectly. If your post type slug is case_study (with an underscore), your template must be single-case_study.php, not single-case-study.php (with a hyphen). The slug in the filename must match the post type slug exactly, and WordPress uses underscores.

Mistake 2: Forgetting fallback templates. You create archive-product.php but your code breaks halfway through. You didn't define a fallback like archive.php, so the site crashes instead of gracefully falling back to index.php. Always ensure at least index.php exists and works.

Mistake 3: Assuming a template will auto-reload after changes. If WordPress has already loaded a template and cached it (or if a caching plugin is active), your new single-product.php won't show up until you clear the cache. Query Monitor can help you verify which file is loaded.

Mistake 4: Not checking archive registration. You create an archive-product.php but forgot to set 'has_archive' => true when registering the post type. The archive URL never gets created, and your template never gets called. Always verify the post type registration.

Key Takeaways

  • The WordPress template hierarchy is a cascade: the most specific template wins, with automatic fallback to less specific ones.
  • Custom post types use single-{post_type}.php and archive-{post_type}.php to separate styling from your main single.php and archive.php templates.
  • Category and taxonomy templates follow a similar hierarchy, checking for slug-specific templates before falling back to generic ones.
  • Query Monitor reveals exactly which template is loaded and shows the full hierarchy that was checked, saving hours of debugging.
  • File naming must match post type and taxonomy slugs exactly, including underscores vs. hyphens.
  • Always ensure a working fallback chain, ideally down to index.php, so the site never breaks.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles