WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

(By "Page Template," I mean a theme file with the "Template Name" header that can be selected in the "Template" dropdown field on the page admin.)

In multiple instances, I've built page templates that could have been accomplished by hooking to the_content or pre_get_posts or something similar. That's lead me to wonder whether there's a way to "register" a selectable theme template in functions.php or a plugin without creating the theme file itself.

Reasons why I want to do this:

  • In the scenarios I have in mind, I'm usually just copying page.php almost verbatim. That means any future change to page.php needs to get made twice. This is even more of a pain with updates to child themes.
  • I like the "Template" field UI. It makes sense to my clients more than some of the alternatives (see below) that I can think of.

In my case, I can think of some workarounds like using a custom meta box or a shortcode (to insert content that's otherwise held in a template). I might even be able to do this (which I just thought of but haven't tested yet):

/*
Template Name: My Template
*/

get_template_part( 'page' );

But in the end, I keep coming back to this idea, so I'm curious whether anyone knows how to do it. If you think it's a horrible idea, I'm interested in that opinion too.

=======

UPDATE: I should add that:

  • I know that there's a $templates array for each theme from this support forums thread which is essentially doing the opposite of what I want to do.
  • I know that there's a hidden custom field _wp_page_template from this WPSE thread.

I guess I just need to understand how the templates array and custom fields work together (and I'm also fuzzy on how the hidden custom meta fields work).

=======

UPDATE II: 10/8/12 The theme backend has been significantly reworked since this question was asked so the above $templates array no longer exists.

share|improve this question

Pages are generally speaking rather inflexible, you would be better off using a custom post type. Also hooking into the actual page template dropdown would very likely be problematic (I doubt it would work without some serious hacking around). You're better off just writing your own meta box dropdown.

I think your on the the right track with get_template_part. It's easy, flexible and really helps with structuring complex output, and using both parameters helps. It arguably easier to copy/paste chunks of code into templates then writing all sorts of whacky functions, and it makes more sense for other people or when you re-visit the code down the road.

The other alternative is someone similar but at the function level, using the template_redirect action.

You could have functions based on your meta selection that loads unique templates, actually now that I think about it you could probably combine it with get_template_part. Code below is not tested.

Something like:

function my_special_template() {
//page "contact" for example, make this your meta dropdown selection
    if (is_page('Contact')){  
        get_template_part( 'special-chunk' );  //file named special-chunk.php
    }
}

add_action('template_redirect', 'my_special_template');

At the end of the day you still grabbing some sort of .php file though.

share|improve this answer
    
Thanks for sharing the comments. I'm curious about why your recommendation to use a custom post type. I'm not quite sure where you're coming from there. One of my big desires with registering a custom template would be piggy-backing off an existing template from the Template Hierarchy. – mrwweb May 9 '12 at 17:05
    
You're better off using template_include. See my answer to this question. – Stephen Harris May 9 '12 at 17:15
    
Stephen Harris is right template_include is much better I was just writing without thinking. As for using a CPT it's just more flexible and easier to work with then pages, for example creating your own meta box dropdown for your client. Using template_include or get_template_part doesn't make "piggy backing" really any different, but CPT's have a lot more options. – Wyck May 10 '12 at 4:04
up vote 1 down vote accepted

I still run into the desire to do this kind of thing and have settled on the following solution which I proposed in the OP and like for a couple reasons:

  1. It's very transparent and feels consistent with keeping the logic in the theme.
  2. It makes it very easy to customize a template later if needed.

Custom Template

Here's the entire template file if I don't need any custom markup. Let's say it's in /wp-content/themes/my-theme/templates/my-template.php

<?php
/**
 * Template Name: My Template
 */
get_template_part( 'page' );

Using it

This now gives us two "hooks" to do all sorts of things with:

  1. A body class of .page-template-my-template for CSS.
  2. The ability to use if( is_page_template( 'templates/my-template.php' ) ) {...

I've used #1 to change layouts, colors, etc., and I've used #2 to filter the_content(), add post_classes, set up custom fields with ACF, and more. Sometimes I use them in combination for something like a full-width template that still requires a sidebar. It's pretty darn powerful and yet very easy to setup and understand if you didn't set it up yourself.

This is still limited to a theme (you couldn't do it with a plugin), but for the purposes of my question, this has been a good answer for me.

share|improve this answer

I wonder if you mean to register a file-less template with the theme_page_templates filter like this:

/**
 * Add a file-less page template to the page template dropdown 
 */
add_filter( 'theme_page_templates', function( $page_templates, $wp_theme, $post ) 
{
    // Edit this to your needs
    $fileless_theme_slug  = 'my_fileless_page_template';
    $fileless_theme_label = esc_html__( 'My File-Less Page Template', 'my-domain' );

    // Append if it doesn't already exists  
    if( ! isset( $page_templates[$fileless_theme_slug] ) )
        $page_templates[$fileless_theme_slug] = $fileless_theme_label ;

    return $page_templates;
}, PHP_INT_MAX, 3 );

showing up like this in the backend:

file-less template

and then modify it's output with:

/**
 * OUtput for our file-less page template
 */
add_action( 'template_redirect', function() 
{   
    // Target pages with out custom template        
    if( is_page_template( 'my_fileless_page_template' ) )
    {
        // Stop the default page template to be displayed
        add_filter( 'template_include', '__return_null', PHP_INT_MAX );

        // Custom output
        my_fileless_page_template_output();
    }
}, PHP_INT_MAX );

Here we define the my_fileless_page_template_output() to our needs e.g. include the theme's header and footer, the main query loop or even template parts. I think it's alright to skip exit at the end.

share|improve this answer
    
+1 for a great answer! I think in the process of figuring out this question, I realized that my question was actually about how to prevent duplicate markup in templates for simple template changes. However, you answered the literal question quite nicely. Thanks! I bet it'll be useful to someone. – mrwweb Jun 6 at 17:21
    
It's an interesting question you asked here and hopefully this can help e.g. those who need to do this from a plugin. I also tried a different approach via PHP stream wrapper aka Kaiser's great answer here but I wasn't successful in injecting some content to a virtual file injected via the template_include filter. I had a quick look at the vfsStream project mentioned by gmazzap. Maybe there's a way to use an approach like how the withContent() method is used in vfsStream? @mrwweb – birgire Jun 6 at 18:07

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.