WordPress Template Hierarchy
Learning Objectives
- Understand how WordPress decides which template file to use
- Master the complete template hierarchy from most specific to most generic
- Learn to create custom templates for different content types
- Implement conditional tags to control template behavior
- Build flexible themes using the template hierarchy effectively
Introduction to Template Hierarchy
The WordPress Template Hierarchy is the system that determines which template file WordPress uses to display different types of content. Think of it as a decision tree where WordPress looks for the most specific template first, then falls back to more general templates if the specific one doesn't exist.
How Template Selection Works
When a visitor requests a page on your WordPress site, WordPress determines what type of query it is (home page, single post, category archive, etc.) and then looks for template files in a specific order.
Visual Template Hierarchy Map
Complete Template Hierarchy Reference
Here's the complete hierarchy for all content types, from most specific to most general:
1. Home Page
// Home Page Display
front-page.php // Used for both static front page and blog homepage
├── home.php // Blog posts index when static front page is set
└── index.php // Ultimate fallback
// Note: front-page.php takes precedence over home.php
// Use conditional tags to handle both cases in front-page.php
2. Single Post
// Single Post Display
single-{post-type}-{post_name}.php // single-product-widget.php
├── single-{post-type}.php // single-product.php
├── single.php // All single posts
├── singular.php // Fallback for single posts and pages
└── index.php // Ultimate fallback
// Examples:
// Post with slug "hello-world" in post type "product":
// 1. single-product-hello-world.php
// 2. single-product.php
// 3. single.php
// 4. singular.php
// 5. index.php
3. Single Page
// Page Display
custom-template.php // Page Template selected in editor
├── page-{page_name}.php // page-about.php
├── page-{id}.php // page-42.php
├── page.php // All pages
├── singular.php // Fallback for single posts and pages
└── index.php
// Custom Page Template Example:
/*
Template Name: Full Width Layout
Template Post Type: page, post
*/
4. Category Archive
// Category Archive Display
category-{slug}.php // category-news.php
├── category-{id}.php // category-5.php
├── category.php // All categories
├── archive.php // All archives
└── index.php
// Example for category "News" with ID 5:
// 1. category-news.php
// 2. category-5.php
// 3. category.php
// 4. archive.php
// 5. index.php
5. Tag Archive
// Tag Archive Display
tag-{slug}.php // tag-wordpress.php
├── tag-{id}.php // tag-10.php
├── tag.php // All tags
├── archive.php // All archives
└── index.php
6. Custom Taxonomy Archive
// Custom Taxonomy Archive Display
taxonomy-{taxonomy}-{term}.php // taxonomy-product_cat-electronics.php
├── taxonomy-{taxonomy}.php // taxonomy-product_cat.php
├── taxonomy.php // All custom taxonomies
├── archive.php // All archives
└── index.php
7. Author Archive
// Author Archive Display
author-{nicename}.php // author-john.php
├── author-{id}.php // author-3.php
├── author.php // All authors
├── archive.php // All archives
└── index.php
8. Date Archive
// Date Archive Display
date.php // All date archives
├── archive.php // All archives
└── index.php
// Date archives include:
// - Yearly: example.com/2024/
// - Monthly: example.com/2024/12/
// - Daily: example.com/2024/12/25/
9. Search Results
// Search Results Display
search.php // Search results page
└── index.php // Ultimate fallback
10. 404 Error
// 404 Not Found Display
404.php // Error page
└── index.php // Ultimate fallback
11. Attachment Pages
// Attachment Display
{MIME-type}-{subtype}.php // image-jpeg.php
├── {subtype}.php // jpeg.php
├── {MIME-type}.php // image.php
├── attachment.php // All attachments
├── single-attachment.php
├── single.php
├── singular.php
└── index.php
// MIME type examples:
// - image (image/jpeg, image/png)
// - video (video/mp4)
// - audio (audio/mpeg)
// - application (application/pdf)
12. Embeds
// Embed Template (for oEmbed)
embed-{post-type}-{post-format}.php
├── embed-{post-type}.php
├── embed.php
└── wp-includes/theme-compat/embed.php
Custom Post Type Archives
Custom post types follow a similar hierarchy pattern:
// Custom Post Type Archive
archive-{post-type}.php // archive-product.php
├── archive.php // All archives
└── index.php // Ultimate fallback
// Example: Product post type
// 1. archive-product.php
// 2. archive.php
// 3. index.php
// Register archive in post type
register_post_type('product', array(
'public' => true,
'has_archive' => true, // or 'products' for custom slug
'rewrite' => array('slug' => 'products'),
// ... other arguments
));
Conditional Tags
WordPress provides conditional tags to determine what type of page is being displayed. These are essential for controlling template behavior:
<?php
// Primary Conditional Tags
if (is_home()) {
// Blog homepage (posts page)
}
if (is_front_page()) {
// Site front page (home or static page)
}
if (is_single()) {
// Any single post
if (is_single('42')) {
// Single post with ID 42
}
if (is_single('hello-world')) {
// Single post with slug
}
}
if (is_page()) {
// Any single page
if (is_page('about')) {
// Specific page by slug
}
if (is_page(array('about', 'contact'))) {
// Multiple pages
}
}
if (is_category()) {
// Category archive
if (is_category('news')) {
// Specific category
}
}
if (is_tag()) {
// Tag archive
}
if (is_tax()) {
// Custom taxonomy archive
if (is_tax('product_cat', 'electronics')) {
// Specific taxonomy term
}
}
if (is_author()) {
// Author archive
if (is_author('john')) {
// Specific author
}
}
if (is_date()) {
// Date-based archive
if (is_year()) { }
if (is_month()) { }
if (is_day()) { }
}
if (is_archive()) {
// Any archive page
}
if (is_search()) {
// Search results page
}
if (is_404()) {
// 404 error page
}
if (is_attachment()) {
// Attachment page
}
if (is_singular()) {
// Any single post, page, or attachment
if (is_singular('product')) {
// Single custom post type
}
}
if (is_post_type_archive()) {
// Custom post type archive
if (is_post_type_archive('product')) {
// Specific post type archive
}
}
// Useful Helper Conditionals
if (is_sticky()) {
// Sticky post
}
if (is_paged()) {
// Paginated page (page 2, 3, etc.)
}
if (has_post_thumbnail()) {
// Post has featured image
}
if (comments_open()) {
// Comments are open
}
if (is_user_logged_in()) {
// User is logged in
}
if (is_admin()) {
// Admin area (not for front-end checks!)
}
// Combining Conditionals
if (is_single() && !is_attachment()) {
// Single post but not attachment
}
if (is_home() || is_archive()) {
// Blog home or any archive
}
if (is_page() && !is_page('contact')) {
// Any page except contact
}
Template Hierarchy Best Practices
- Start with index.php and build up - ensure your theme works with just index.php
- Create specific templates only when needed to avoid theme bloat
- Use template parts (get_template_part()) to avoid code duplication
- Name templates clearly and follow WordPress naming conventions
- Document custom page templates with clear names and descriptions
- Use conditional tags in parent templates instead of creating many specific templates
- Test your templates with different content types and edge cases
- Consider child themes when planning template structure
- Use singular.php as a smart fallback for both posts and pages
Creating Custom Page Templates
WordPress allows you to create custom templates that users can select when creating pages:
Basic Page Template
<?php
/*
Template Name: Full Width Template
Template Post Type: page, post
*/
get_header(); ?>
<div class="full-width-container">
<?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>'); ?>
</header>
<div class="entry-content">
<?php the_content(); ?>
</div>
</article>
<?php endwhile; ?>
</div>
<?php get_footer(); ?>
Advanced Custom Template with Options
<?php
/*
Template Name: Portfolio Gallery
Template Post Type: page
Description: A custom template for displaying portfolio items in a grid
*/
get_header();
// Get custom field values (if using ACF)
$columns = get_field('gallery_columns') ?: 3;
$items_per_page = get_field('items_per_page') ?: 12;
?>
<div class="portfolio-template">
<header class="page-header">
<?php the_title('<h1>', '</h1>'); ?>
<?php if (has_excerpt()) : ?>
<div class="page-description">
<?php the_excerpt(); ?>
</div>
<?php endif; ?>
</header>
<?php
// Query portfolio items
$paged = get_query_var('paged') ? get_query_var('paged') : 1;
$portfolio = new WP_Query(array(
'post_type' => 'portfolio',
'posts_per_page' => $items_per_page,
'paged' => $paged
));
if ($portfolio->have_posts()) : ?>
<div class="portfolio-grid columns-<?php echo esc_attr($columns); ?>">
<?php while ($portfolio->have_posts()) : $portfolio->the_post(); ?>
<div class="portfolio-item">
<a href="<?php the_permalink(); ?>" class="portfolio-link">
<?php if (has_post_thumbnail()) : ?>
<?php the_post_thumbnail('portfolio-thumb'); ?>
<?php endif; ?>
<div class="portfolio-overlay">
<h3><?php the_title(); ?></h3>
<span class="portfolio-category">
<?php echo get_the_term_list(get_the_ID(), 'portfolio_category', '', ', '); ?>
</span>
</div>
</a>
</div>
<?php endwhile; ?>
</div>
<!-- Pagination -->
<div class="pagination">
<?php
echo paginate_links(array(
'total' => $portfolio->max_num_pages,
'current' => $paged
));
?>
</div>
<?php wp_reset_postdata(); ?>
<?php endif; ?>
</div>
<?php get_footer(); ?>
Template Parts and Organization
Use template parts to organize reusable components and keep templates clean:
<?php
// get_template_part() usage
// Basic usage
get_template_part('template-parts/content');
// Looks for: template-parts/content.php
// With suffix
get_template_part('template-parts/content', 'single');
// Looks for:
// 1. template-parts/content-single.php
// 2. template-parts/content.php
// Dynamic suffix
get_template_part('template-parts/content', get_post_type());
// For 'product' post type, looks for:
// 1. template-parts/content-product.php
// 2. template-parts/content.php
// Passing variables to template parts (WordPress 5.5+)
$args = array(
'title' => get_the_title(),
'show_meta' => true,
'layout' => 'grid'
);
get_template_part('template-parts/content', 'card', $args);
// In template-parts/content-card.php:
$title = $args['title'] ?? '';
$show_meta = $args['show_meta'] ?? false;
$layout = $args['layout'] ?? 'list';
?>
<!-- Organized theme structure -->
themes/your-theme/
├── index.php
├── style.css
├── functions.php
├── header.php
├── footer.php
├── sidebar.php
├── single.php
├── page.php
├── archive.php
├── search.php
├── 404.php
├── template-parts/
│ ├── content.php
│ ├── content-single.php
│ ├── content-page.php
│ ├── content-none.php
│ ├── content-search.php
│ ├── header/
│ │ ├── site-branding.php
│ │ └── site-navigation.php
│ └── footer/
│ ├── site-info.php
│ └── footer-widgets.php
├── page-templates/
│ ├── full-width.php
│ ├── portfolio.php
│ └── contact.php
└── assets/
├── css/
├── js/
└── images/
Real World Example: Smart Archive Template
Creating a flexible archive.php that handles multiple content types intelligently:
<?php
/**
* Archive Template
* Handles all archive pages with dynamic layouts
*/
get_header(); ?>
<div class="archive-wrapper">
<header class="archive-header">
<?php
// Dynamic archive title
if (is_category()) {
echo '<h1 class="archive-title">Category: ' . single_cat_title('', false) . '</h1>';
if (category_description()) {
echo '<div class="archive-description">' . category_description() . '</div>';
}
} elseif (is_tag()) {
echo '<h1 class="archive-title">Tag: ' . single_tag_title('', false) . '</h1>';
} elseif (is_author()) {
$author = get_queried_object();
echo '<div class="author-header">';
echo get_avatar($author->ID, 120);
echo '<div class="author-info">';
echo '<h1 class="archive-title">Author: ' . $author->display_name . '</h1>';
echo '<div class="author-bio">' . $author->description . '</div>';
echo '</div></div>';
} elseif (is_date()) {
if (is_day()) {
echo '<h1 class="archive-title">Daily Archives: ' . get_the_date() . '</h1>';
} elseif (is_month()) {
echo '<h1 class="archive-title">Monthly Archives: ' . get_the_date('F Y') . '</h1>';
} elseif (is_year()) {
echo '<h1 class="archive-title">Yearly Archives: ' . get_the_date('Y') . '</h1>';
}
} elseif (is_post_type_archive()) {
echo '<h1 class="archive-title">' . post_type_archive_title('', false) . '</h1>';
} elseif (is_tax()) {
$term = get_queried_object();
$taxonomy = get_taxonomy($term->taxonomy);
echo '<h1 class="archive-title">' . $taxonomy->labels->singular_name . ': ' . $term->name . '</h1>';
if ($term->description) {
echo '<div class="archive-description">' . $term->description . '</div>';
}
} else {
echo '<h1 class="archive-title">Archives</h1>';
}
// Results count
global $wp_query;
if ($wp_query->found_posts > 0) {
echo '<p class="results-count">Found ' . $wp_query->found_posts . ' results</p>';
}
?>
</header>
<?php if (have_posts()) : ?>
<div class="archive-content <?php echo esc_attr(get_archive_layout_class()); ?>">
<?php
while (have_posts()) : the_post();
// Use different templates for different post types
$post_type = get_post_type();
// Try post-type specific template first
if (locate_template('template-parts/content-' . $post_type . '.php')) {
get_template_part('template-parts/content', $post_type);
// Fall back to post format
} elseif (get_post_format()) {
get_template_part('template-parts/content', get_post_format());
// Default template
} else {
get_template_part('template-parts/content', 'archive');
}
endwhile;
?>
</div>
<!-- Smart Pagination -->
<?php
the_posts_pagination(array(
'mid_size' => 2,
'prev_text' => '← Previous',
'next_text' => 'Next →',
'before_page_number' => '<span class="screen-reader-text">Page </span>'
));
?>
<?php else : ?>
<div class="no-results">
<h2>Nothing Found</h2>
<p>
<?php
if (is_search()) {
echo 'Sorry, but nothing matched your search terms. Please try again with different keywords.';
} else {
echo 'It seems we can\'t find what you\'re looking for. Perhaps searching can help.';
}
?>
</p>
<?php get_search_form(); ?>
</div>
<?php endif; ?>
</div>
<?php
// Include sidebar for certain archives
if (should_show_sidebar()) {
get_sidebar();
}
get_footer();
// Helper functions (in functions.php)
function get_archive_layout_class() {
if (is_post_type_archive('portfolio')) {
return 'grid-layout columns-3';
} elseif (is_category('news') || is_tag()) {
return 'list-layout with-excerpts';
} else {
return 'default-layout';
}
}
function should_show_sidebar() {
// No sidebar for portfolio archives
if (is_post_type_archive('portfolio')) {
return false;
}
// No sidebar for author archives
if (is_author()) {
return false;
}
// Show sidebar for everything else
return true;
}
?>
Practice Exercise
Create a custom theme with intelligent template selection:
View Solution
// Theme file structure
themes/smart-theme/
├── index.php // Ultimate fallback
├── front-page.php // Home page
├── single.php // Single posts
├── single-product.php // Single product posts
├── page.php // Single pages
├── archive.php // All archives
├── archive-product.php // Product archive
├── category.php // Category archives
├── search.php // Search results
├── 404.php // Error page
├── header.php
├── footer.php
├── functions.php
├── style.css
├── template-parts/
│ ├── content.php
│ ├── content-single.php
│ ├── content-page.php
│ ├── content-product.php
│ ├── content-excerpt.php
│ └── content-none.php
└── page-templates/
└── template-portfolio.php
// index.php - Smart fallback
<?php get_header(); ?>
<main id="main" class="site-main">
<?php
if (have_posts()) :
// Determine content layout based on context
if (is_home() && !is_front_page()) : ?>
<header>
<h1 class="page-title">Blog</h1>
</header>
<?php endif;
echo '<div class="content-area">';
while (have_posts()) : the_post();
// Smart template part selection
if (is_singular()) {
get_template_part('template-parts/content', 'single');
} elseif (is_search()) {
get_template_part('template-parts/content', 'excerpt');
} else {
get_template_part('template-parts/content', get_post_type());
}
endwhile;
echo '</div>';
the_posts_navigation();
else :
get_template_part('template-parts/content', 'none');
endif;
?>
</main>
<?php
get_sidebar();
get_footer();
// single-product.php - Custom post type template
get_header(); ?>
<div class="product-single">
<?php while (have_posts()) : the_post(); ?>
<article id="product-<?php the_ID(); ?>" <?php post_class(); ?>>
<div class="product-images">
<?php
if (has_post_thumbnail()) {
the_post_thumbnail('large');
}
// Product gallery
$gallery = get_field('product_gallery');
if ($gallery) : ?>
<div class="product-gallery">
<?php foreach ($gallery as $image) : ?>
<img src="<?php echo esc_url($image['sizes']['thumbnail']); ?>"
alt="<?php echo esc_attr($image['alt']); ?>">
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<div class="product-info">
<h1><?php the_title(); ?></h1>
<div class="product-price">
$<?php echo get_field('price'); ?>
</div>
<div class="product-description">
<?php the_content(); ?>
</div>
<div class="product-meta">
<?php
$categories = get_the_terms(get_the_ID(), 'product_category');
if ($categories) : ?>
<div class="product-categories">
<strong>Categories:</strong>
<?php foreach ($categories as $category) : ?>
<a href="<?php echo get_term_link($category); ?>">
<?php echo $category->name; ?>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
</article>
<!-- Related Products -->
<?php
$related = new WP_Query(array(
'post_type' => 'product',
'posts_per_page' => 4,
'post__not_in' => array(get_the_ID()),
'orderby' => 'rand'
));
if ($related->have_posts()) : ?>
<section class="related-products">
<h2>Related Products</h2>
<div class="products-grid">
<?php while ($related->have_posts()) : $related->the_post(); ?>
<?php get_template_part('template-parts/content', 'product'); ?>
<?php endwhile; ?>
</div>
</section>
<?php wp_reset_postdata(); ?>
<?php endif; ?>
<?php endwhile; ?>
</div>
<?php get_footer(); ?>
Practice Assignment
Master the WordPress Template Hierarchy by completing these tasks:
- Create a complete theme with at least 10 different template files
- Implement custom templates for 2 different custom post types
- Build an intelligent archive.php that handles all archive types differently
- Create 3 custom page templates with different layouts
- Use conditional tags to modify template behavior based on context
- Organize your theme with proper template parts structure
- Test your template fallback chain by removing templates