Himanshu SharmaSoftware engineer
Let’s talk

WordPress · Lesson 24 of 45

The template hierarchy

How WordPress chooses a template file for every request, from the most specific (such as category-news.php) down to index.php, in classic and block themes.

14 minClassic templates and queriesUpdated 23 October 2026

By the end of this lessonPredict which template WordPress will use for any address, and add the right file to change the layout of one kind of page.

How WordPress chooses a template

For every request, WordPress first works out what kind of page it is: a single post, a page, a category archive, a search, a 404, the front page. Then it builds a list of template file names, from the most specific to the most general, and uses the first one the theme has. This is the template hierarchy.

For a single blog post called "Hello world!", the list is:

  1. single-post-hello-world.php
  2. single-post.php
  3. single.php
  4. singular.php
  5. index.php

Because index.php ends every list, it is the one file every classic theme must have. Block themes use exactly the same lists with .html files in the templates folder (single.html, index.html and so on), and a block theme's templates win over a classic theme's PHP files.

A theme that shows its template

To watch the hierarchy at work, here is a tiny classic theme whose templates print their own file name. It has a stylesheet header, a shared header and footer, and an index.php:

css
/*
Theme Name: Bakery Classic
Description: A tiny classic theme for learning the template hierarchy.
Version: 1.0.0
*/
php
<!doctype html>
<html <?php language_attributes(); ?>>
<head>
	<meta charset="<?php bloginfo( 'charset' ); ?>">
	<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<header><p class="site-title"><a href="<?php echo esc_url( home_url( '/' ) ); ?>"><?php bloginfo( 'name' ); ?></a></p></header>
php
<footer><p>Fresh bread daily</p></footer>
<?php wp_footer(); ?>
</body>
</html>
php
<?php get_header(); ?>
<main>
	<p class="template">index.php</p>
	<?php while ( have_posts() ) : the_post(); ?>
		<h2><?php the_title(); ?></h2>
	<?php endwhile; ?>
</main>
<?php get_footer(); ?>

get_header() and get_footer() include header.php and footer.php. wp_head() and wp_footer() are essential: WordPress and plugins print styles, scripts and meta tags through them. The while ( have_posts() ) part is the Loop, covered in the next lesson.

bash
wp theme activate bakery-classic
result
Success: Switched to 'Bakery Classic' theme.

With only index.php, every page uses it:

http
GET /hello-world/
result
index.php
http
GET /sample-page/
result
index.php

Adding more specific templates

Now add single.php:

php
<?php get_header(); ?>
<main>
	<p class="template">single.php</p>
	<?php while ( have_posts() ) : the_post(); ?>
		<h1><?php the_title(); ?></h1>
		<?php the_content(); ?>
	<?php endwhile; ?>
</main>
<?php get_footer(); ?>
http
GET /hello-world/
result
single.php

The post now uses single.php, while the Sample Page still uses index.php, because single.php is only in the list for posts. Pages have their own list:

  1. a custom page template chosen in the editor (a file with a Template Name: header);
  2. page-{slug}.php, for example page-sample-page.php;
  3. page-{id}.php, for example page-2.php;
  4. page.php;
  5. singular.php;
  6. index.php.
php
<?php get_header(); ?>
<main><p class="template">page.php</p><?php while ( have_posts() ) : the_post(); the_title( '<h1>', '</h1>' ); the_content(); endwhile; ?></main>
<?php get_footer(); ?>
php
<?php get_header(); ?>
<main><p class="template">page-sample-page.php</p><?php while ( have_posts() ) : the_post(); the_title( '<h1>', '</h1>' ); endwhile; ?></main>
<?php get_footer(); ?>
bash
wp post create --post_type=page --post_title="About" --post_status=publish
result
Success: Created post 4.
http
GET /sample-page/
result
page-sample-page.php
http
GET /about/
result
page.php

The Sample Page uses its own page-sample-page.php; the new About page falls back to page.php. Slug-based templates are handy for one special page, but they break if someone changes the page's slug, so a custom page template (chosen in the editor) is often more robust.

Archives, search and 404

The lists for other kinds of page:

Request Candidates, in order
Category archive category-{slug}.php, category-{id}.php, category.php, archive.php, index.php
Tag archive tag-{slug}.php, tag-{id}.php, tag.php, archive.php, index.php
Author archive author-{nicename}.php, author-{id}.php, author.php, archive.php, index.php
Date archive date.php, archive.php, index.php
Custom post type archive archive-{post_type}.php, archive.php, index.php
Search results search.php, index.php
Not found 404.php, index.php
Front page front-page.php, then the home or page rules
Blog posts page home.php, index.php
php
<?php get_header(); ?>
<main><p class="template">archive.php</p><?php the_archive_title( '<h1>', '</h1>' ); ?></main>
<?php get_footer(); ?>
http
GET /category/uncategorized/
result
archive.php
Category: Uncategorized
http
GET /this-does-not-exist/
result
index.php

The category archive used archive.php (there is no category.php). The missing page fell all the way back to index.php, because the theme has no 404.php; WordPress still sends the 404 status, but the visitor sees an empty list. A real theme should always have a 404.php (or 404.html).

Seeing the hierarchy in code

WordPress exposes the candidate lists through filters such as single_template_hierarchy. You can print the list for a post:

php
<?php
$post = get_post( 1 );
$templates = [ "single-{$post->post_type}-{$post->post_name}.php", "single-{$post->post_type}.php", 'single.php' ];
echo "candidates: ", implode( ' > ', $templates ), "\n";
echo "chosen: ", basename( locate_template( $templates ) ), "\n";
result
candidates: single-post-hello-world.php > single-post.php > single.php
chosen: single.php

locate_template() returns the first file in the list that exists in the child or parent theme. The plugin Query Monitor shows the chosen template and the full hierarchy for any page, which is invaluable when debugging themes.

Common mistakes

  • Putting everything in index.php with many if checks, instead of using the hierarchy.
  • Relying on page-{slug}.php for pages whose slug may change.
  • No 404.php, so "not found" pages look like empty listings.
  • Forgetting wp_head() or wp_footer(), which silently breaks plugins and the admin toolbar.

Exercise

Which template would WordPress use for the tag archive /tag/vegan/ in a theme that has index.php, archive.php and tag.php? Which file would you add to give only the vegan tag its own layout?

Show one possible answer

It uses tag.php, which comes before archive.php in the tag list. To change only the vegan tag, add tag-vegan.php, which is checked first. Everything else keeps using tag.php.

Key points

  • For each request WordPress builds a list of candidate template names, from most to least specific, and uses the first one the theme has.
  • Single posts try single-{post-type}-{slug}, single-{post-type}, single, singular, then index.
  • Pages try a custom template, page-{slug}, page-{id}, page, singular, then index; categories try category-{slug}, category-{id}, category, archive, index.
  • Block themes follow the same hierarchy with .html files in templates/ instead of .php files.

Check yourself

1. A theme has index.php, single.php and singular.php. Which is used for a blog post?
2. Which file would make only the category with the slug news look different?
3. What file must every classic theme have?
4. Which template does WordPress try first for the site's front page?