Control where users go and what they see
Two WordPress hooks that decide where users go and what they see.
Building a WordPress theme can be a bit like untangling spaghetti. But by using template_redirect and template_include you can gain a lot of control over where your users go and what they see.
Sometimes you need to display different layouts based on which user is logged in or the state of your application. This can lead to complex nested routes and tangled template hierarchies.
Introducing the template_redirect and template_include hooks.
These useful WordPress filters can help you to take more control over the layouts presented to your visitors, and insert custom templates or take other actions based on the current URL and state of the user’s current session.
template_include – used to override WordPress’ default template behaviours. This is executed just before WordPress includes the default template file for a given route. Use this to get more control over what users see.template_redirect – used to override what WordPress does before it starts to load the default template. You use this hook to determine where users go.
They’re similar, but the difference between them is important. Only use template_redirect to guide a user
to another part of your site. Don’t use it to load a different template, that’s what
template_include is for!
When the template_redirect and template_include hooks run, WordPress has already done the heavy
lifting, has populated the global objects, and has full knowledge of the state of the current page. In other words, it’s
a great time to make a decision as to where you want the user to go next or what they should see on the page.
You can see why this gives you a lot more control over your template appearance and functionality. Use that power wisely!
There’s a small but important distinction between these two hooks. template_redirect is designed
only for redirecting a user to another page or location (hence the name!). Use this if you want to
template_redirect is the perfect place to check if a user should be blocked from accessing a resource
The reason why this is not the place to be placing other code to adjust the template layout is that
WordPress may be relying on additional functionality hooked into template_redirect. If you try to output a
template at this point then exit to terminate the script, that additional functionality may not run.
One example use for template_redirect could be to disable the single page view for a given post type but only
for guest users.
add_action('template_redirect', 'single_view_disable');
/**
* Redirect away from the single view for my_custom_post_type only
* if the user is not logged in
*/
function single_view_disable() {
global $post;
if($post->post_type == 'my_custom_post_type' && is_single() && !is_user_logged_in()):
// Redirect back home
wp_redirect(home_url(), 301);
exit;
endif;
}
2026 update Prefer wp_safe_redirect() over wp_redirect() for internal
redirects — it validates the destination against the site’s allowed hosts, closing off open-redirect risks.
The other hook, template_include would be used if you wanted to display a different template than the
one which will be served up normally as part of WordPress’ process flow rather than redirecting the user to an
entirely new page.
You might use template_include to
template_include is an excellent way to give your plugin control over the front-end appearancetemplate_include is a perfect place to force specific templates for testing purposesTo give another specific example, modifying the example above, say we want to have two different layouts for logged in and non-logged in users for a given post type.
add_action('template_include', 'single_view_adjustment');
/**
* Redirect away from the single view for my_custom_post_type only
* if the user is not logged in
*/
function single_view_adjustment($template) {
global $post;
if($post->post_type == 'my_custom_post_type' && is_single() && is_user_logged_in()):
// Search for the new template file either within the parent
// or child themes
$new_template = locate_template(array('my_custom_post_type-single-for-auth-users.php'));
if ( '' != $new_template ) {
return $new_template;
} // if the template doesn't exist, WordPress will load
// the default template instead
endif;
return $template;
}
2026 update template_include is a filter, not an action — hook it with
add_filter() and always return $template so you pass the value along the chain (as the example
correctly does). It works exactly the same on block/FSE themes: the filter still runs before the canvas is rendered, so
this technique is entirely valid in the block era.
Note here, our use of the locate_template() function. This handy little helper function returns the path to a
given template file, allowing for optional overrides within the parent/child themes.
You will also need to create a file my_custom_post_type-single-for-auth-users.php within your theme folder. To
get started, you can copy the contents of single.php into this file.
These two hooks are great places to run functionality which is dependent on the overall state of WordPress’ objects
for any given URL, but to keep things tidy, it’s best to pop any non-template-related code into another hook. The
init hook is a great place to do that.
add_action('init', 'do_some_stuff');
function do_some_stuff()
{
// And here we go...
}
All articles
I help content-rich organisations understand their data and pull it back into one place. Tell me what specific problems you're having and I'll be happy to talk solutions with you.
Tell me what specific problems you’re having and I’ll be happy to talk solutions with you.