iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Template Hierarchy

When a URL hits a WordPress site, the system runs through the template hierarchy to decide which PHP file in your theme renders the page. Knowing the order saves hours of "why isn’t my template applying?".

Theme files + most-specific-first rules

EXAMPLE
<?php
// 1) The hierarchy in plain words
// WordPress walks a list of candidate files for each request and uses the FIRST one
// that exists in the active theme. From most-specific to most-generic.

// Single post:
//   single-{post-type}-{slug}.php          e.g. single-post-hello-world.php
//   single-{post-type}.php                  e.g. single-product.php (custom post type)
//   single.php
//   singular.php
//   index.php                                ← always the final fallback

// Static page:
//   custom template file (Page Attributes)
//   page-{slug}.php
//   page-{id}.php
//   page.php
//   singular.php
//   index.php

// Category archive:
//   category-{slug}.php                      e.g. category-news.php
//   category-{id}.php
//   category.php
//   archive.php
//   index.php

// 2) Other archives
// Tag archive:
//   tag-{slug}.php → tag-{id}.php → tag.php → archive.php → index.php

// Custom taxonomy:
//   taxonomy-{taxonomy}-{term}.php → taxonomy-{taxonomy}.php → taxonomy.php → archive.php → index.php

// Author archive:
//   author-{nicename}.php → author-{id}.php → author.php → archive.php → index.php

// Date archive (year/month/day):
//   date.php → archive.php → index.php

// Custom post type archive:
//   archive-{post-type}.php → archive.php → index.php

// 3) Special pages
//   front-page.php       — home page (overrides everything when present)
//   home.php             — blog index
//   404.php              — anything that doesn't resolve
//   search.php           — search results
//   attachment.php       — single attachment
//   privacy-policy.php   — assigned privacy page

// 4) Inspect what was picked (debugging)
add_action( 'template_redirect', function () {
    add_filter( 'template_include', function ( $template ) {
        if ( current_user_can( 'manage_options' ) ) {
            error_log( '[wp-template] ' . $_SERVER['REQUEST_URI'] . ' → ' . $template );
        }
        return $template;
    }, 99 );
} );

// Or install Query Monitor plugin — Templates panel shows the hierarchy walk

// 5) Standard single-post template
<?php get_header(); ?>

<main id="main" class="site-main">
    <?php if ( have_posts() ) : ?>
        <?php while ( have_posts() ) : the_post(); ?>
            <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
                <header class="entry-header">
                    <?php the_title( '<h1 class="entry-title">', '</h1>' ); ?>
                    <p class="entry-meta">
                        <?php echo esc_html( get_the_date() ); ?>
                        — by <?php the_author(); ?>
                    </p>
                </header>
                <div class="entry-content">
                    <?php the_content(); ?>
                </div>
                <?php if ( comments_open() ) comments_template(); ?>
            </article>
        <?php endwhile; ?>
    <?php else : ?>
        <p>No posts found.</p>
    <?php endif; ?>
</main>

<?php get_sidebar(); ?>
<?php get_footer(); ?>

// 6) A page template for a specific slug
// File: page-pricing.php
<?php
/**
 * Template Name: Pricing landing
 */
get_header(); ?>

<section class="pricing-hero">
    <h1><?php the_title(); ?></h1>
    <?php the_content(); ?>
</section>

<?php get_footer(); ?>

// page-{slug}.php applies automatically; 'Template Name' lets editors pick it for ANY page.

// 7) Block themes (FSE) — different mental model
// Block themes use HTML template files instead of PHP:
//   theme-root/templates/single.html
//   theme-root/templates/page.html
//   theme-root/templates/category-news.html
//
// The hierarchy itself is identical; only the file extension and editor differ.

// 8) Custom queries inside a template
<?php
$featured = new WP_Query( array(
    'post_type'      => 'product',
    'posts_per_page' => 6,
    'meta_query'     => array(
        array( 'key' => 'featured', 'value' => '1', 'compare' => '=' ),
    ),
    'orderby'        => 'menu_order',
    'order'          => 'ASC',
) );

if ( $featured->have_posts() ) :
    while ( $featured->have_posts() ) : $featured->the_post(); ?>
        <div class="feature">
            <h3><?php the_title(); ?></h3>
            <?php the_excerpt(); ?>
        </div>
    <?php endwhile;
    wp_reset_postdata();      // ALWAYS reset after a custom WP_Query
endif;
?>

// 9) Filter the template choice at runtime
add_filter( 'template_include', function ( $template ) {
    if ( is_singular( 'product' ) && get_post_meta( get_the_ID(), 'use_modern_template', true ) ) {
        $override = locate_template( 'single-product-modern.php' );
        if ( $override ) return $override;
    }
    return $template;
} );

// 10) Custom post type with its own archive + single
register_post_type( 'event', array(
    'public'      => true,
    'has_archive' => true,                    // enables /events/
    'rewrite'     => array( 'slug' => 'events' ),
    'supports'    => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
    'labels'      => array( 'name' => 'Events', 'singular_name' => 'Event' ),
) );
// WP now looks for: archive-event.php, single-event-{slug}.php, single-event.php

// 11) Common bugs
//   • Template named page-About.php (capital A) on a Linux server → not picked (case-sensitive!)
//   • Custom slug doesn't match — page-{slug}.php uses the URL slug, not the post title
//   • Editing template-parts/header.php directly instead of using get_header()/get_template_part()
//   • Forgetting wp_reset_postdata() after a secondary WP_Query → wrong data in the next loop
//   • Adding 'Template Name' to a single-{post-type}.php — Template Name only works for pages
//   • Block theme template overrides not appearing — site editor caches; clear theme caches

Why it matters

Name your template files after the URL they should match: page-about.php, archive-event.php, category-news.php. WordPress picks the most specific one that exists, so a new file usually beats a setting tweak — install Query Monitor and check the Templates panel whenever the “wrong” layout shows up.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// WP picks templates from most-specific to least-specific.
// single-{post-type}.php > single.php > singular.php > index.php
Try it Yourself »

Discussion

Loading…