How to move page templates to custom folder?

/YOUR_THEME/page-templates/ will only work for custom page templates assigned on the admin page edit screen, not for page-$slug or page-$id named templates.

The correct filter hook in my view is page_template, but you don’t (I assume!) want to throw out any other possible templates for your pages, not least because you’re bound to have some pages on your site for which you haven’t made a /my-sub-dir/page-$slug.php template file.

The page_template filter hook is called just after WP has found a template for the page using the standard template hierarchy. It would be handy if there was a filter to let you inject your additional template into the right part of the template hierarchy, but in the absence of that we’ll need to replicate the search for page templates from WordPress’s own get_page_template() function, found in /wp-includes/template.php:

function get_page_template() {
    $id = get_queried_object_id();
    $template = get_page_template_slug();
    $pagename = get_query_var('pagename');

    if ( ! $pagename && $id ) {
        // If a static page is set as the front page, $pagename will not be set. Retrieve it from the queried object
        $post = get_queried_object();
        if ( $post )
            $pagename = $post->post_name;
    }

    $templates = array();
    if ( $template && 0 === validate_file( $template ) )
        $templates[] = $template;
    if ( $pagename )
        $templates[] = "page-$pagename.php";
    if ( $id )
        $templates[] = "page-$id.php";
    $templates[] = 'page.php';

    return get_query_template( 'page', $templates );
}

This function builds an array of possible templates for Pages. get_query_template() then uses locate_template() to run through the array and return the filename of the first template found.

As we can’t hook into the list of proposed templates, we’ll sadly have to duplicate some of this work.

Here’s our own function:

function tbdn_get_page_template() {
    $id = get_queried_object_id();
    $template = get_page_template_slug();
    $pagename = get_query_var('pagename');

    if ( ! $pagename && $id ) {
        // If a static page is set as the front page, $pagename will not be set. Retrieve it from the queried object
        $post = get_queried_object();
        if ( $post )
            $pagename = $post->post_name;
    }

    $templates = array();

    if ( $template && 0 === validate_file( $template ) )
        $templates[] = $template;
    // if there's a custom template then still give that priority

    if ( $pagename )
        $templates[] = "our-sub-dir/page-$pagename.php";
    // change the default search for the page-$slug template to use our directory
    // you could also look in the theme root directory either before or after this

    if ( $id )
        $templates[] = "our-sub-dir/page-$id.php";
    $templates[] = 'page.php';

    /* Don't call get_query_template again!!!
       // return get_query_template( 'page', $templates );
       We also reproduce the key code of get_query_template() - we don't want to call it or we'll get stuck in a loop .
       We can remove lines of code that we know won't apply for pages, leaving us with...
    */

    $template = locate_template( $templates );

    return $template;
}

add_filter( 'page_template', 'tbdn_get_page_template' );

Caveats:

1 – I haven’t tested this, but if you’re up to messing around with the template hierarchy then you should certainly be able to follow, test & adjust my code which is mostly copied from WP anyway.

2 – If future core code ever changes the template hierarchy for pages then the code above will go out-of-date.

Leave a Comment