Add Page Templates to WordPress with a Plugin

Add Page Templates to WordPress with a Plugin

Have you ever wanted to create your own page templates, but not had access to the theme itself? I, as a WordPress plugin author, have found this problem to be particularly annoying when developing my plugins. Thankfully the solution is quite simple! I’m going to take you quickly through the few lines of code which you will need to dynamically create WordPress Page Templates directly through PHP.

Inspiration for this article and the genius behind the code solution comes from Tom McFarlin: I am using my edited version of his original code, which you can find on his GitHub. I have kept his commenting in (as well as adding some of my own) as I find it very helpful in explaining what is going on – I couldn’t have said it any better myself!

You can find the code in its entirety and an example plugin at the very bottom of this post.

Shall we begin?

THE CODE

We will create our PHP function using a PHP Class. For those of you who aren’t well versed with PHP Classes, a Class is defined as an object which contains a collection of functions and variables which are working together. Check out the PHP.net Introduction for more details about the syntax and theory.

Our wrapper will only need 3 variables:

  1. The Plugin Slug: This is simply used as a unique identifier for the plugin.
  2. Class Instance: As we are adding an instance of this class to WordPress’ head, we’d better store it.
  3. Template Array: As you can probably guess, this is an array holding the template names and titles.

Here they are in code:

class PageTemplater {

		/**
         * A Unique Identifier
         */
		 protected $plugin_slug;

        /**
         * A reference to an instance of this class.
         */
        private static $instance;

        /**
         * The array of templates that this plugin tracks.
         */
        protected $templates;

Get Class Instance

As I said previously, we’ll be adding an instance of our class to the WordPress header using the add_filter() function. Therefore we will need a method which will return (or create) this instance for us.

For this, we will need a simple method, which will be called ‘get_instance’. Check it out below;

        /**
         * Returns an instance of this class. 
         */
        public static function get_instance() {

                if( null == self::$instance ) {
                        self::$instance = new PageTemplater();
                } 

                return self::$instance;

        }

This will be the method called when our class is added to the WordPress head using ‘add_action()’.

WordPress Filters

Now we’ve sorted out the ‘get_instance’ method, we need to sort out what happens when it is actually instantiated.

We will use WordPress’s inbuilt add_filter() function to add an instance of our class into key points along the WordPress initialisation timeline. Using this method we will insert our page templates’ data into relevant slots, such as telling WordPress what file to use as a template when the page is called, and the title to display on the dropdown menu on the Page Editor.

For this we need to use the ‘__construct’ method (this will be run when the class is instantiated).

        /**
         * Initializes the plugin by setting filters and administration functions.
         */
        private function __construct() {

                $this->templates = array();


                // Add a filter to the attributes metabox to inject template into the cache.
                add_filter(
					'page_attributes_dropdown_pages_args',
					 array( $this, 'register_project_templates' ) 
				);


                // Add a filter to the save post to inject out template into the page cache
                add_filter(
					'wp_insert_post_data', 
					array( $this, 'register_project_templates' ) 
				);


                // Add a filter to the template include to determine if the page has our 
				// template assigned and return it's path
                add_filter(
					'template_include', 
					array( $this, 'view_project_template') 
				);


                // Add your templates to this array.
                $this->templates = array(
                        'goodtobebad-template.php'     => 'It\'s Good to Be Bad',
                );
				
        }

There are 4 different things going on here (ignoring ‘ $this->templates = array();’, which is just preparing the variable for use);

  1. Lines 9 – 13: This filter is adding the ‘register_project_templates’ to the ‘page_attributes_dropdown_pages_args’ hook. This is populating the WordPress cache with our new templates, ‘tricking’ WordPress into believing that the page template files actually exist in the template directory. This adds the page templates to the dropdown list on the page attributes meta box in the page editor.
  2. Lines 16 – 20: Here we are doing essentially the same as the previous code block, except this time we are adding our page template (if selected) to the saved post data as well.
  3. Lines 23 – 28: This filter is adding the ‘template_include’ to the ‘view_project_template’ hook. This is a very important function; this tells WordPress where your page template file actually is. WordPress will use the path provided by this to render the final page.
  4. Lines 31 – 34: Although this is simple, it is very important. This is where you specify the page templates you want to be added, and the path relative to the file where the page template file is ( eg. ‘something.php’ ). I’ve included one example (which will be used in the example plugin). Check out below for a general example ;
                $this->templates = array(
                        'FILE_PATH_AND_NAME'     => 'TEMPLATE_TITLE',
                        // EG.
                        'awesome-template.php'     => 'Awesome',
                        'templates/organised-template.php'     => 'Organised',
                );

(Eat, Sleep,) Code, Repeat as necessary.

register_project_templates()

I’ve alluded to this method previously; let’s see what it actually does.

Essentially, the purpose of this method is to manipulate WordPress’s cache, inserting the relevant data about our page templates at the right places. Have a look at the code first, and I’ll talk you through it afterwards.

        public function register_project_templates( $atts ) {

                // Create the key used for the themes cache
                $cache_key = 'page_templates-' . md5( get_theme_root() . '/' . get_stylesheet() );

                // Retrieve the cache list. 
				// If it doesn't exist, or it's empty prepare an array
                $templates = wp_get_theme()->get_page_templates();
                if ( empty( $templates ) ) {
                        $templates = array();
                } 

                // New cache, therefore remove the old one
                wp_cache_delete( $cache_key , 'themes');

                // Now add our template to the list of templates by merging our templates
                // with the existing templates array from the cache.
                $templates = array_merge( $templates, $this->templates );

                // Add the modified cache to allow WordPress to pick it up for listing
                // available templates
                wp_cache_add( $cache_key, $templates, 'themes', 1800 );

                return $atts;

        }

Right then. Line 4 is the first place to look. As you may have guessed, we are generating a ‘cache key’. This will be used as a unique identifier for our page template data. Using the md5() function simply creates a unique string identifier to avoid any conflicts.

Next, on line 8, we are searching for and retrieving the page template cache (if it exists already): this will return an array of paths and titles. On lines 9-11 we check to see if there was any output from the cache query. If yes, great. If not, create a local array to hold the data we will be merging into the cache.

The next step is crucial. On line 14 we delete the existing page template cache. Don’t worry, no data is lost – it is stored in the $templates variable.

On line 18 we merge the existing page templates cache with our new entries, and on line 22 we re-insert the whole page template cache into WordPress’s system.

Simple!

view_project_template ()

We’re now onto our final method; this is where we tell WordPress where the real page template file is.

        /**
         * Checks if the template is assigned to the page
         */
        public function view_project_template( $template ) {

                global $post;

                if (!isset($this->templates[get_post_meta( 
					$post->ID, '_wp_page_template', true 
				)] ) ) {
					
                        return $template;
						
                } 

                $file = plugin_dir_path(__FILE__). get_post_meta( 
					$post->ID, '_wp_page_template', true 
				);
				
                // Just to be safe, we check if the file exist first
                if( file_exists( $file ) ) {
                        return $file;
                } 
				else { echo $file; }

                return $template;

        } 

Ok then, this method will be checking against the global $post variable (line 6). It checks to see if a page template ( ‘_wp_page_template’ ) has been set for the post (meaning it must be a page). If not, then never mind – non-pages can’t have page templates.

Line 16 specifies the location of the page template file. As I have set out above, it checks for the specified page template file in the root directory of your plugin. (This can be easily changed though; see below.)

                // Just changing the page template path
                // WordPress will now look for page templates in the subfolder 'templates',
                // instead of the root
                $file = plugin_dir_path(__FILE__). 'templates/' .get_post_meta( 
					$post->ID, '_wp_page_template', true 
				);

After this, on lines 21 – 24, we have just a little bit of validation which checks if the file actually exists. If yes, awesome! If not, oh dear… You will most likely get a PHP error message if WordPress can’t find the template file, or even a blank screen. If any of these symptoms sounds familiar, just check the template file path by printing the $file variable to the screen.

If you plan to use this code commercially (which you are free to do – my version of the code comes with no license, hence you are free to do with it as you please), I truly recommend investing some time in error handling for maximum reliability.

That is that. With our class completed, there is only one thing left to do – add it to the WordPress head.

add_action( 'plugins_loaded', array( 'PageTemplater', 'get_instance' ) );

Congrats if you made it all the way through! I hope you found what I had to say useful, and you benefit from it in the future!

ENTIRE CODE

class PageTemplater {

		/**
         * A Unique Identifier
         */
		 protected $plugin_slug;

        /**
         * A reference to an instance of this class.
         */
        private static $instance;

        /**
         * The array of templates that this plugin tracks.
         */
        protected $templates;


        /**
         * Returns an instance of this class. 
         */
        public static function get_instance() {

                if( null == self::$instance ) {
                        self::$instance = new PageTemplater();
                } 

                return self::$instance;

        } 

        /**
         * Initializes the plugin by setting filters and administration functions.
         */
        private function __construct() {

                $this->templates = array();


                // Add a filter to the attributes metabox to inject template into the cache.
                add_filter(
					'page_attributes_dropdown_pages_args',
					 array( $this, 'register_project_templates' ) 
				);


                // Add a filter to the save post to inject out template into the page cache
                add_filter(
					'wp_insert_post_data', 
					array( $this, 'register_project_templates' ) 
				);


                // Add a filter to the template include to determine if the page has our 
				// template assigned and return it's path
                add_filter(
					'template_include', 
					array( $this, 'view_project_template') 
				);


                // Add your templates to this array.
                $this->templates = array(
                        'goodtobebad-template.php'     => 'It\'s Good to Be Bad',
                );
				
        } 


        /**
         * Adds our template to the pages cache in order to trick WordPress
         * into thinking the template file exists where it doens't really exist.
         *
         */

        public function register_project_templates( $atts ) {

                // Create the key used for the themes cache
                $cache_key = 'page_templates-' . md5( get_theme_root() . '/' . get_stylesheet() );

                // Retrieve the cache list. 
				// If it doesn't exist, or it's empty prepare an array
                $templates = wp_get_theme()->get_page_templates();
                if ( empty( $templates ) ) {
                        $templates = array();
                } 

                // New cache, therefore remove the old one
                wp_cache_delete( $cache_key , 'themes');

                // Now add our template to the list of templates by merging our templates
                // with the existing templates array from the cache.
                $templates = array_merge( $templates, $this->templates );

                // Add the modified cache to allow WordPress to pick it up for listing
                // available templates
                wp_cache_add( $cache_key, $templates, 'themes', 1800 );

                return $atts;

        } 

        /**
         * Checks if the template is assigned to the page
         */
        public function view_project_template( $template ) {

                global $post;

                if (!isset($this->templates[get_post_meta( 
					$post->ID, '_wp_page_template', true 
				)] ) ) {
					
                        return $template;
						
                } 

                $file = plugin_dir_path(__FILE__). get_post_meta( 
					$post->ID, '_wp_page_template', true 
				);
				
                // Just to be safe, we check if the file exist first
                if( file_exists( $file ) ) {
                        return $file;
                } 
				else { echo $file; }

                return $template;

        } 


} 

add_action( 'plugins_loaded', array( 'PageTemplater', 'get_instance' ) );

THE PLUGIN

Grab the plugin on our Github. Just click on the image below.

POST EDITOR CLOSEUP

Here’s a close up of the plugin in action. See the page template added under Page Attributes?

GTBB1

Harri Bell-Thomas
Harri Bell-Thomas is a young, talented Wordpress Developer who works as the developer for InfinityCode. Check out his Graphic Design portfolio on his website, hbt.io.
Harri Bell-Thomas
Harri Bell-Thomas

Latest posts by Harri Bell-Thomas (see all)

This article has 45 comments
  1. AJ Clarke says:

    I actually didn’t know how to do this and it’s going to come in handy for a new plugin I’m developing for Authentic themes ;) Thanks for being a contributor with us and sharing your knowledge ;)

    Admin
    1. Harri Bell-Thomas says:

      Thanks AJ! Its great to be a part of such a great blog!

      Author
  2. Trisha Cupra says:

    Nice! I’ve been wondering how to create a Page Template via a plugin. Saving this straight to Evernote for future reference.

  3. Karl says:

    Do you have any updates for the registration of the page templates in the dropdown? I noticed that it deletes the list of templates provided by the theme, and only lists my custom plugin templates.

    1. Harri Bell-Thomas says:

      Hey there Karl,

      I’m sorry you are having this problem. I can see where this error is probably coming from, so I’ll have a look and try and find out what’s going awry!

      I’ll update the article and leave another reply here when I have more news and hopefully a solution.

      Thanks, Harri

      Author
      1. Harri Bell-Thomas says:

        I think I’ve figured it out – the problem lay in retrieving the pre-existing page template cache. I will update the post now, but if you would like to apply the fix manually, simply change;

        $templates = wp_cache_get( $cache_key, ‘themes’ ); (Line 92 in pagetemplater.php of the plugin) to

        $templates = wp_get_theme()->get_page_templates(); (This is probably a nicer solution in retrospect)

        I hope this fixes the problem :)

        P.S. Pretty proud of the 20 minute response time ;)

        Author
  4. Yash says:

    Very nice article, working for me !!!. I need to know how can i use this code to my plugin. I used this code to my plugin and then my SMS feature functionality is break. when i comment this file code the SMS feature is again working fine. Please tell me how to properly use this code to plugin.

    Thanks and regards,

    Yash

    1. AJ Clarke says:

      Sorry for the delay on this. We’ve updated the code if you want to check it out!

      Admin
  5. Mohamed Hamad says:

    I recently used this plugin/code in one of my own plugins and it worked fantastically. But now i’m noticing that the template added via this code is the only template available to choose from. Is there something to be updated to make this more current?

  6. Spence says:

    This seems like a great idea and exactly what I need! I have downloaded the code, added my custom templates to: $this->templates = array(
    I have added ‘templates/’ to: $file = plugin_dir_path(__FILE__). ‘templates/’
    I can activate the plugin in my wp-admin but, it is not showing any of the templates in the page editor template drop-down. I have read and reread this article a and all I can find that Tom McFarlin has posted about this method and I still have the same problem.

    Does anyone have any ideas of what I might be doing wrong or know how I could figure it out?

    1. Harri Bell-Thomas says:

      Hello Spence,

      Sorry for the delay in replying. Could we possibly discuss this over on Github, as it’s easier to talk through code there.

      Thanks!
      Harri

      Author
      1. Spence says:

        Hello Harri,

        Thank you for the reply. I figured it out. Details are on the WordPress forms here.

        This method works really great! Thanks for all this helpful info.

        I did run into one bug that doesn’t stop it from working. It is something that would just be nice to figure out. I detail the minor problem in the WP post but I will explain it again here for anyone else following this.

        Using your great explanation and code above, the new templates that I have created show up nicely in the template drop-down in page editors. However, they do not show up in the list of pages under “Quick Edit” template drop-down options. It would be nice to have them show up in the “Quick Edit” options too, for connivance.

        I would be happy to continue this conversation on Github.

        1. Spence says:

          Hello again Harri,

          I got this bug worked out, that was not showing page templates in the “Quick Edit” options of the pages list. It was resolved here if you are interested.

          Thanks for posting this helpful info about this plugin method for page templates, it has been a big help.

          1. Harri Bell-Thomas says:

            Thanks Spence – that is actually a really nice solution!

            Author
  7. Mushtaq Ali says:

    Great post. I am able to add template in the drop down however Template code does not works. Somehow wordpress can’t find this new templstr created by the plugin. Any help? Thanks

    1. Harri Bell-Thomas says:

      Hello Mushtaq,

      This is probably a problem with the path to the template file. Have you edited this line in your code?

      $file = plugin_dir_path(__FILE__). get_post_meta( $post->ID, ‘_wp_page_template’, true );

      If not, are you sure you have the template file in the root folder of your plugin. If yes, just double check that your new location matches where you keep the files.

      If none of the above works, then could you pop over to Github, as it’s easier for us to talk code there.

      Thanks!
      Harri

      Author
      1. Mushtaq Ali says:

        Hi, Thanks for the reply, I had changed the line as below :
        $file = plugin_dir_path( __FILE__ ) . ‘templates/’ . get_post_meta( $post->ID, ‘_wp_page_template’, true );

        Also I have kept the template file under myplugin/templates/mytemplate.php and pageTemplater.php in includes directory. The structure is as follows:

        myplugindir/myplugin.php
        myplugindir/includes/pageTemplater.php
        myplugindir/templates/template.php

        P.S: I am new to github, I really don’t know how to create comment/ issue. Thanks

        1. Harri Bell-Thomas says:

          That seems to be ok – what have you put for this;

          $this->templates = array(
          . . .
          );
          (Line 72) ?
          Harri

          Author
          1. Mushtaq Ali says:
            $this->templates = array(
            'mytemplate.php' => 'New Template',
            );
            
            1. Harri Bell-Thomas says:

              That is odd…
              Does mytemplate.php contain a valid Page Template declaration header?
              e.g;

              /*
               * Template Name: My Custom Page
               * Description: A Page Template with a darker design.
               */
              

              Harri

              Author
  8. Aamer Hussain Khan says:

    This worked out perfectly for me. Thank you for the code! :-)

  9. Craig Ralston says:

    Awesome guide – I am having one issue and I can’t seem to narrow down where it is coming from. My template is being found and is working properly but above my header it is spitting out the file path to the template file. Any ideas what could be causing this? I have also read through all of the comments and made appropriate changes where needed and double checked issues others were having. Everything seems like it should be working properly, just not sure why the file path is being echo’d in the header.

    Thanks!

  10. Vishal says:

    Hello Harri,
    I want to create page template for frontend & this seems to be for admin section. Can you help me to tell how can i solve my requirement? Any suggestions.
    Thanks,
    Vishal

    1. AJ Clarke says:

      Hi Vishal,

      It’s the same thing. Have a look at the CODEX for creating the page template file.

      Admin
  11. cyrusville says:

    Thanks for the great tutorial. Is it possible to register a custom header template using the same approach? This should not appear on the drop down.

  12. opportunex says:

    Awesome solution! Thanks. This is really going to be a big time saver :) You Rock!

  13. Jim Douglas Mazabuel says:

    Muchas muchas gracias! Una y otra vez lo digo, gracias por toda tu ayuda!

  14. Pixselig says:

    First: thamk you verry much for your outstanding solution, Harri! It works perfect for me!

    But I have a little question:

    I am writting a PlugIn, this PlugIn injects different Page-Templates into a existing Theme. For this, I use your awesome code. It works perfect.
    But, i also want to enqueue a Script only on Pages who uses the injected Page-Templates.
    For this I use the WP-Function “is_page_template()” http://codex.wordpress.org/Function_Reference/is_page_template
    But it doesnt work, whatever data path I try.

    For better understanding whats going on, I output two diferent Parameters (WP-Functions) inside the Template.
    For the first “get_page_template_slug( $post->ID );”, I get the right name of the injected Page-Template back.
    But for the second “get_page_template(__FILE__);” I get the absolute path to the default Template inside the used Theme (page.php) back.

    So, I am a litte bit confused, dont know how to enqueue Scripts only on the injected Pagetemplates.

    Hope, you understand my bad english -> sorry for that!
    But if you have a question, i will answer!!
    If you need some Code-Snippets from me, i will post!!

    Thamks,
    Pixselig

  15. Pixselig says:

    Sorry, for second post!
    I only want to say, that I got it to work!

    My brain was fried – the answer is very easy:
    “is_page_template()” can only be called afte the template has been loaded. So I had to add some wp-action:

    function check_for_template() {
       if ( is_page_template ( "xxx.php" ) {
          function add_template_scripts() {
             wp_register_script( 'template_script', ................ );
             wp_enqueue_script( 'template_script' );
          }
          add_action( 'wp_enqueue_scripts', 'add_template_scripts' );
       }
    }
    add_action('wp','check_for_template');

    This solution had nothing to do with the great code from Harri – its the common way to check for templates in WP-functions.

    Thanks twice Harri!
    Pixselig

  16. Robby says:

    Any pointers on how to get this to work with a custom post? I’m creating the custom post via a plugin, and would like to include the archive and post template in the plugin.

    Thanks!

  17. Alexis Duran (@duranmla) says:

    Thanks! It works perfectly for me. Nice help. @Robby I’m doing the same thing I would suggest you to include the class like:

    include_once('path/to/page-templater.php');

    into your main plugin file and it is all since:

    add_action( 'plugins_loaded', array( 'PageTemplater', 'get_instance' ) ); // once the plugin is activated the class make its magic!!

    Regards!

Leave a Reply