When WordPress handles a request it determines which PHP template file to load by walking a predetermined lookup list — from the most specific template name to the most generic fallback. For a single post of the custom post type movie with the slug the-godfather, WordPress checks for single-movie-the-godfather.php first, then single-movie.php, then single.php, then singular.php, then index.php, loading the first file it finds in the active theme (child theme takes priority over parent theme at every step). The hierarchy works the same way for every request type — taxonomy archives, author pages, category pages, date archives, search results, and 404 pages each have their own lookup chain. Understanding this hierarchy means you can create precisely targeted templates for individual post types, taxonomies, and even specific terms or authors without writing any PHP conditional code inside the template file.
Problem: Your theme's single.php has grown into a file full of if ( get_post_type() === 'movie' ) and if ( is_page_template('team.php') ) conditionals, making it hard to maintain and extend.
Solution: Split the conditionals into separate template files named according to the hierarchy — single-movie.php, archive-movie.php — and let WordPress load the correct one automatically. Use the template_include filter for plugin-level overrides that must work regardless of the active theme.
Common hierarchy lookup chains — WordPress uses the first file it finds:
SINGLE POST (post type 'movie', slug 'the-godfather'):
single-movie-the-godfather.php
single-movie.php
single.php
singular.php
index.php
ARCHIVE (post type 'movie'):
archive-movie.php
archive.php
index.php
TAXONOMY TERM (taxonomy 'genre', term 'action'):
taxonomy-genre-action.php
taxonomy-genre.php
taxonomy.php
archive.php
index.php
CATEGORY (slug 'news'):
category-news.php
category-3.php ← term ID fallback
category.php
archive.php
index.php
AUTHOR (login 'john-doe'):
author-john-doe.php
author-7.php ← user ID fallback
author.php
archive.php
index.php
PAGE (template field set to 'contact' in post meta):
templates/contact.php ← if declared with Template Name: comment
page-contact.php
page-42.php
page.php
singular.php
index.php
Override template selection from a plugin with the template_include filter (runs after the theme hierarchy is resolved):
<?php
add_filter( 'template_include', 'my_plugin_override_template' );
function my_plugin_override_template( $template ) {
// Override single 'movie' template with a file bundled in the plugin
if ( is_singular( 'movie' ) ) {
$plugin_template = plugin_dir_path( __FILE__ ) . 'templates/single-movie.php';
if ( file_exists( $plugin_template ) ) {
return $plugin_template;
}
}
return $template; // return unchanged for all other requests
}
NOTE: The template_include filter is the correct tool for plugin-level template overrides because it runs after the theme hierarchy has been resolved and after child theme lookups. It ensures your plugin's template is only used when no theme template matches — you can check get_template_part() in the returned $template path to see which file the theme resolved to, then decide whether to substitute your own.