Josh is the co-founder of CalderaWP, which authors top-quality WordPress plugins. They’ve just released a new plugin: Easy Queries, an awesome new visual editor for WordPress queries.
Learn more at CalderaWP, and get a 20% discount on Easy Queries when you use the code SHOUT! at checkout.
The WordPress template hierarchy is the system that defines which PHP template file your theme (or child theme) will use to display a given content type on your WordPress site. As such, it allows you to customize how your theme shows different content, based on context. Combined with a strong set of template tags, most of the time it covers every situation you need.
Of course, WordPress can’t handle every requirement for every site, so there’s also a set of hooks that you can use to customize how the template hierarchy behaves. While these hooks are generally not used in themes for general release, they are very helpful when building a theme for specific site, or in plugin development.
If you are not familiar with the template hierarchy yet, you should familiarize yourself with it before reading this article.
In this article, I am going to walk you through three hooks—the second of which comes in many flavors—that you can use to customize the behaviour of WordPress’ template loader. This article will go in the same order that WordPress uses to execute these hooks as it works to determine which template file to load from the current theme.
Bypassing The Template Loader with template_redirect
The first hook that fires is
. This is an action that runs before the template is determined. While there is a history of using this to set which template to use, that is very bad practice and had a lot of negative side effects. Instead, template_redirect
template_redirect
should be used in cases when you need to prevent loading a template at all.
This action’s most important purpose is that it is a way to respond to a request without loading a template from the theme at all. For example, the WordPress REST API hooks in at template_redirect
, creates a JSON response, returns it, and ends the session—all without ever hitting the template loader, which is not needed.
This is a useful strategy if, for example, you are creating another type of API, such as a custom API for processing front-end AJAX requests, or for triggering a regular event using an external cron service, or anything else that doesn’t require the use of your theme. Just make sure to use the function exit()
to end the session, to prevent WordPress from continuing to the template loader.
One excellent use for template_redirect
is to lock out non-logged in users from viewing a specific page. For example:
add_action( 'template_redirect', function() {
if ( ! is_user_logged_in() && is_page( 'members-only-content' ) ) {
wp_redirect( wp_login_url() );
exit();
}
});
Notice that in the example code above, I was able to use the function is_page()
—since the main query is actually run right before template_redirect
fires. This is part of why tempalte_redirect
is so useful. If we hook in any earlier it is very hard to know the context of the current request.
Filtering Individual Parts Of The Template Hierarchy
Once template_redirect
is passed, WordPress loads up the actual template hierarchy system, to determine which type of template it should use. For every template file it could attempt to load, there is a filter. This filter uses a dynamically generated name based on the file that it should load.
This means that when WordPress determines that it needs to load single-post.php
, before it does so, it fires the filter single-post_template
. This allows you to change what template is loaded for a specific type of file.
What’s really cool, is that WordPress fires these filters even if the relevant file doesn’t exist in the current theme. So single-post_template
fires before WordPress falls back to the next template it would try and load, in this case single.php
.
Here’s an example of how we can use this filter, in a plugin, to provide a default template for the single post view of a post type called hats
. Note that this only takes effect if WordPress didn’t already find a single-hats.php
template in the current theme or child theme:
add_filter( 'single-hats_template', function( $template ) [
if ( ! $template ) {
$template = dirname( __FILE__ ) . '/templates/default-hats-single.php';
}
return $template;
});
Notice that I didn’t use is_singular( 'hats' )
here to check that WordPress is on a single post of the hats custom post type. That check isn’t needed, since this filter will only ever run in that context.
The Catch-All Filter: template_include
Once WordPress has run the whole template hierarchy system, it runs one more filter before the template is actually loaded. That filter is called template_include
and its result is passed directly to include()
to actually include the template.
This filter is commonly used in place of the dynamically named filters that I discussed in the last section, probably because those filters are poorly documented. As a result, developers often use template_include
, with a string comparison on the template being called, to conditionally intercept specific templates. That works, but is not optimal.
The reason why it’s better to use the dynamically-named filters is that the template hierarchy runs a series of foreach loops to see if the current template it is trying to load exits. The sooner you intercept this process, the sooner it finishes and the faster the template loads.
Since template_include
runs last, it can be used as a catch-all. I used it recently, in a WordPress-powered app I was prototyping to make it so only one template file would ever be used. For apps, or single-page sites, this is a useful strategy.
A cool thing that you can do with template_include
is create a “grandchild theme.” While this isn’t a concept that exists in WordPress, you can create a child theme for a child theme, using a plugin to serve as the grandchild theme. In the plugin’s main file, you would add a function like this:
add_filter( 'template_include', function( $template ) {
$path = explode('/', $template );
$template_chosen = end( $path );
$grandchild_template = dirname( __FILE__ ) . '/' . $template_chosen;
if ( file_exists( $grandchild_template ) ) {
$template = $grandchild_template;
}
return $template;
});
The callback function hooked to template_include
determines the name of the template WordPress is about to load, and uses it to build a path to the template in the plugin. It then checks if that file actually exists. If it does, then it tells WordPress to load that file. If not, then nothing changes, and the same fallbacks—to child theme, then parent theme—take effect.
Go Forth And Customize
If you’re creating themes to be released to the public, you need to respect the template hierarchy. On the other hand, when you’re working on a theme or child theme for a specific site, these hooks are very useful for handling client requirements that no WordPress default can accommodate. In addition, a knowledge of these filters becomes very handy for plugin developers: they allow you to create templates for displaying content from your plugin.
I hope this article helped you not only learn how to customize the WordPress template hierarchy, but encourages you to dive into WordPress core, find these filters, and see how the template hierarchy works. It’s a neat system, and understanding how it works will benefit your WordPress problem solving skills enormously.