How Page Templates are loaded:
According to the default WordPress Template Hierarchy, a page
request loads a template based on the priority and naming as stated below:
Custom Page Template
: if defined in the page editor.
page-{slug}.php
page-{url-encoded-slug}.php
: only for multi-byte characters.
page-{id}.php
page.php
singular.php
index.php
Among these, singular.php
and index.php
are not actually page templates. singular.php
is the fallback template for any single post types and index.php
is the ultimate fallback template for anything that a WordPress template is supposed to load. So the first five are page templates.
How to inject template files from a sub-directory within the hierarchy:
WordPress core function get_page_template()
generates the necessary page
template hierarchy array and just before deciding exactly which template file to load from the hierarchy, WordPress fires the page_template_hierarchy
filter hook. So the best way to add a sub-directory, where WordPress will look for page-{slug}.php
templates automatically, is to use this filter and inject proper file names relative to that sub-directory within the page template hierarchy array.
Note: the original filter hook is a dynamic filter hook defined as {$type}_template_hierarchy
, which is located in wp-includes/template.php
file. So when the $type
is page
, the filter hook becomes page_template_hierarchy
.
Now, for our purpose, we'll inject the sub-directory/page-{slug}.php
file name just before page-{slug}.php
within the template hierarchy array passed to the hooks callback function. That way, WordPress will load sub-directory/page-{slug}.php
file if it exists, otherwise it'll follow the normal page template loading hierarchy. Of course, to maintain consistency, we'll still give Custom Page Template
a higher priority compared to our sub-directory/page-{slug}.php
file. So the modified page template hierarchy will become:
Custom Page Template
: if defined in the page editor.
sub-directory/page-{slug}.php
sub-directory/page-{url-encoded-slug}.php
: only for multi-byte characters.
page-{slug}.php
page-{url-encoded-slug}.php
: only for multi-byte characters.
page-{id}.php
page.php
Sample functions.php
CODE:
If you plan to do this change only on a single theme, then you may use the following CODE in your active theme's functions.php
file:
// defining the sub-directory so that it can be easily accessed from elsewhere as well.
define( 'WPSE_PAGE_TEMPLATE_SUB_DIR', 'page-templates' );
function wpse312159_page_template_add_subdir( $templates = array() ) {
// Generally this doesn't happen, unless another plugin / theme does modifications
// of their own. In that case, it's better not to mess with it again with our code.
if( empty( $templates ) || ! is_array( $templates ) || count( $templates ) < 3 )
return $templates;
$page_tpl_idx = 0;
if( $templates[0] === get_page_template_slug() ) {
// if there is custom template, then our page-{slug}.php template is at the next index
$page_tpl_idx = 1;
}
$page_tpls = array( WPSE_PAGE_TEMPLATE_SUB_DIR . '/' . $templates[$page_tpl_idx] );
// As of WordPress 4.7, the URL decoded page-{$slug}.php template file is included in the
// page template hierarchy just before the URL encoded page-{$slug}.php template file.
// Also, WordPress always keeps the page id different from page slug. So page-{slug}.php will
// always be different from page-{id}.php, even if you try to input the {id} as {slug}.
// So this check will work for WordPress versions prior to 4.7 as well.
if( $templates[$page_tpl_idx] === urldecode( $templates[$page_tpl_idx + 1] ) ) {
$page_tpls[] = WPSE_PAGE_TEMPLATE_SUB_DIR . '/' . $templates[$page_tpl_idx + 1];
}
array_splice( $templates, $page_tpl_idx, 0, $page_tpls );
return $templates;
}
add_filter( 'page_template_hierarchy', 'wpse312159_page_template_add_subdir' );
Sample Plugin:
If you want to follow the same template file organization in multiple themes, then it's best to keep this feature separate from your theme. In that case, instead of modifying the theme's functions.php
file with the above sample CODE, you'll need to create a simple plugin with the same sample CODE.
Save the following CODE with a file name e.g. page-slug-template-subdir.php
within your WordPress plugins
directory:
<?php
/*
Plugin Name: WPSE Page Template page-slug.php to Sub Directory
Plugin URI: https://wordpress.stackexchange.com/a/312159/110572
Description: Page Template with page-{slug}.php to a Sub Directory
Version: 1.0.0
Author: Fayaz Ahmed
Author URI: https://www.fayazmiraz.com/
*/
// defining the sub-directory so that it can be easily accessed from elsewhere as well.
define( 'WPSE_PAGE_TEMPLATE_SUB_DIR', 'page-templates' );
function wpse312159_page_template_add_subdir( $templates = array() ) {
// Generally this doesn't happen, unless another plugin / theme does modifications
// of their own. In that case, it's better not to mess with it again with our code.
if( empty( $templates ) || ! is_array( $templates ) || count( $templates ) < 3 )
return $templates;
$page_tpl_idx = 0;
if( $templates[0] === get_page_template_slug() ) {
// if there is custom template, then our page-{slug}.php template is at the next index
$page_tpl_idx = 1;
}
$page_tpls = array( WPSE_PAGE_TEMPLATE_SUB_DIR . '/' . $templates[$page_tpl_idx] );
// As of WordPress 4.7, the URL decoded page-{$slug}.php template file is included in the
// page template hierarchy just before the URL encoded page-{$slug}.php template file.
// Also, WordPress always keeps the page id different from page slug. So page-{slug}.php will
// always be different from page-{id}.php, even if you try to input the {id} as {slug}.
// So this check will work for WordPress versions prior to 4.7 as well.
if( $templates[$page_tpl_idx] === urldecode( $templates[$page_tpl_idx + 1] ) ) {
$page_tpls[] = WPSE_PAGE_TEMPLATE_SUB_DIR . '/' . $templates[$page_tpl_idx + 1];
}
array_splice( $templates, $page_tpl_idx, 0, $page_tpls );
return $templates;
}
// the original filter hook is {$type}_template_hierarchy,
// which is located in wp-includes/template.php file
add_filter( 'page_template_hierarchy', 'wpse312159_page_template_add_subdir' );
Usage:
With either of the above CODE, WordPress will recognize page-{slug}.php
template files within page-templates
directory of your theme automatically.
Say for example, you have an about
page. So, if it doesn't have a custom page template
set from the editor, then WordPress will look for THEME/page-templates/page-about.php
template file and if that doesn't exist, then WordPress will look for THEME/page-about.php
template file and so on (i.e. the default page template hierarchy).