Template files are very standard for themes and heavily used in child themes. For example, if a user wants to modify the layout of their site pages, they can simply copy page.php from their parent theme to a child theme, modify the HTML structure, and the changes will be reflected on the site. While it’s well known that template files like page.php can also be built for plugins, I feel most developers tend to avoid building them due to the complexity of building a template loader in a plugin. First I’d like to walk you through the logic of how a template file loader works; second I am going to show you a real example; and third I’m going to show you just how easy building a template loader into your plugin is now.

The concept of a template loader is pretty simple:

  1. Determine the name of the file to be loaded
  2. Determine the locations to look for the file and the order in which each should be searched
  3. Search each location for the file
  4. Load the file and stop searching as soon as it is found
  5. Fall back to some default template if the requested file is never found

When building a template loader in a plugin, the developer will usually create one main templates folder (the name can vary) that exists inside the plugin and it will hold all of the available template files. These files can then be copied to a specifically-named folder in the currently active theme in order to be modified. If a file from the plugin’s templates folder exists inside of the theme, it gets loaded instead of the default version from the plugin.

Template file loaders like this are used in a lot of large-scale plugins in order to provide greater flexibility and better control for advanced users that want to tailor a plugin’s output more to their specific needs. A few examples of plugins that use template file loaders are:

Let’s look at a quick example of what a template file loader looks like in a plugin. We will use Restrict Content Pro (RCP) for this example.

RCP has several template files for the registration form, the login form, and the user’s profile editor form. The login form template, as an example, looks like this:

<?php global $rcp_login_form_args; ?>
<?php if( ! is_user_logged_in() ) : ?>
 
	<?php rcp_show_error_messages( 'login' ); ?>
 
	<form id="rcp_login_form"  class="rcp_form" method="POST" action="<?php echo esc_url( rcp_get_current_url() ); ?>">
		<fieldset class="rcp_login_data">
			<p>
				<label for="rcp_user_Login"><?php _e( 'Username', 'rcp' ); ?></label>
				<input name="rcp_user_login" id="rcp_user_login" class="required" type="text"/>
			</p>
			<p>
				<label for="rcp_user_pass"><?php _e( 'Password', 'rcp' ); ?></label>
				<input name="rcp_user_pass" id="rcp_user_pass" class="required" type="password"/>
			</p>
			<p>
				<label for="rcp_user_remember"><?php _e( 'Remember', 'rcp' ); ?></label>
				<input type="checkbox" name="rcp_user_remember" id="rcp_user_remember" value="1"/>
			</p>
			<p class="rcp_lost_password"><a href="<?php echo esc_url( wp_lostpassword_url( rcp_get_current_url() ) ); ?>"><?php _e( 'Lost your password?', 'rcp' ); ?></a></p>
			<p>
				<input type="hidden" name="rcp_action" value="login"/>
				<input type="hidden" name="rcp_redirect" value="<?php echo esc_url( $rcp_login_form_args['redirect'] ); ?>"/>
				<input type="hidden" name="rcp_login_nonce" value="<?php echo wp_create_nonce( 'rcp-login-nonce' ); ?>"/>
				<input id="rcp_login_submit" type="submit" value="Login"/>
			</p>
		</fieldset>
	</form>
<?php else : ?>
	<div class="rcp_logged_in"><?php _e( 'You are logged in.', 'rcp' ); ?> <a href="<?php echo wp_logout_url( home_url() ); ?>"><?php _e( 'Logout', 'rcp' ); ?></a></div>
<?php endif; ?>

This file is mostly straight HTML with a little bit of PHP. A pretty standard template file.

Restrict Content Pro loads this file through a short code, and when that happens, it looks like this:

rcp_get_template_part( 'login' );

The rcp_get_template_part() takes care of searching each of the possible locations for a file called login.php and then loading the file if it exists.

The rcp_get_template_part() function looks like this:

/**
 * Retrieves a template part
 *
 * @since v1.5
 *
 * Taken from bbPress
 *
 * @param string $slug
 * @param string $name Optional. Default null
 *
 * @uses  rcp_locate_template()
 * @uses  load_template()
 * @uses  get_template_part()
 */
function rcp_get_template_part( $slug, $name = null, $load = true ) {
	// Execute code for this part
	do_action( 'get_template_part_' . $slug, $slug, $name );
 
	// Setup possible parts
	$templates = array();
	if ( isset( $name ) )
		$templates[] = $slug . '-' . $name . '.php';
	$templates[] = $slug . '.php';
 
	// Allow template parts to be filtered
	$templates = apply_filters( 'rcp_get_template_part', $templates, $slug, $name );
 
	// Return the part that is found
	return rcp_locate_template( $templates, $load, false );
}

This function does four things:

  1. Fires a do_action() call so that other plugins can hook into the load process of specific template files.
  2. Determines the names of the template files to look for.
  3. Passes the template file names through a filter so other plugins can force it to look for additional or different template files.
  4. Fires rcp_locate_template(), which performs the actual file lookups.

Before we have a final picture of how this works, we need to look at rcp_locate_template():

/**
 * Retrieve the name of the highest priority template file that exists.
 *
 * Searches in the STYLESHEETPATH before TEMPLATEPATH so that themes which
 * inherit from a parent theme can just overload one file. If the template is
 * not found in either of those, it looks in the theme-compat folder last.
 *
 * Taken from bbPress
 *
 * @since v1.5
 *
 * @param string|array $template_names Template file(s) to search for, in order.
 * @param bool $load If true the template file will be loaded if it is found.
 * @param bool $require_once Whether to require_once or require. Default true.
 *                            Has no effect if $load is false.
 * @return string The template filename if one is located.
 */
function rcp_locate_template( $template_names, $load = false, $require_once = true ) {
	// No file found yet
	$located = false;
 
	// Try to find a template file
	foreach ( (array) $template_names as $template_name ) {
 
		// Continue if template is empty
		if ( empty( $template_name ) )
			continue;
 
		// Trim off any slashes from the template name
		$template_name = ltrim( $template_name, '/' );
 
		// Check child theme first
		if ( file_exists( trailingslashit( get_stylesheet_directory() ) . 'rcp/' . $template_name ) ) {
			$located = trailingslashit( get_stylesheet_directory() ) . 'rcp/' . $template_name;
			break;
 
		// Check parent theme next
		} elseif ( file_exists( trailingslashit( get_template_directory() ) . 'rcp/' . $template_name ) ) {
			$located = trailingslashit( get_template_directory() ) . 'rcp/' . $template_name;
			break;
 
		// Check theme compatibility last
		} elseif ( file_exists( trailingslashit( rcp_get_templates_dir() ) . $template_name ) ) {
			$located = trailingslashit( rcp_get_templates_dir() ) . $template_name;
			break;
		}
	}
 
	if ( ( true == $load ) && ! empty( $located ) )
		load_template( $located, $require_once );
 
	return $located;
}

This function takes an array of template file names, such as array( ‘login.php’, ‘login-form.php’ ), loops through the list and searches in each of the possible locations for the files. As soon as it finds a matching file, it does one of two things:

  1. If the third parameter, $load, is true, the file is loaded with the core WordPress function load_template()
  2. If $load is false, it returns the absolute path to the file

For Restrict Content Pro, the locations rcp_locate_template() looks are (in this order):

  • wp-content/themes/CHILD_THEME/rcp/{filename}
  • wp-content/themes/PARENT_THEME/rcp/{filename}
  • wp-content/plugins/restrict-content-pro/templates/{filename}

That’s the entire process. While it is not a ton of code, it is something that can be intimidating to write from scratch (note, I took most of my code straight from bbPress, thanks JJJ!). If you are not writing a large plugin, it can be difficult to justify spending a lot of time on a system like this, which leads me into the next part of this tutorial.

Gary Jones recently released a Template Loader class that does most of the heavy lifting for you. The class is based on the template loader built into Easy Digital Downloads (which was based off of the one in bbPress), so it works nearly identically to the methods described above for Restrict Content Pro, except it takes an OOP approach.

I’m not going to walk you through the class, but I am going to show you a quick example of how to use it so that you can easily build template file loaders into your own plugins.

First, you will copy the main Gamajo_Template_Loader class into a new file in your plugin.

Second, you will write a new class that extends Gamajo_Template_Loader, like this:

<?php
 
/**
 * Template loader for PW Sample Plugin.
 *
 * Only need to specify class properties here.
 *
 */
class PW_Template_Loader extends Gamajo_Template_Loader {
 
	/**
	 * Prefix for filter names.
	 *
	 * @since 1.0.0
	 * @type string
	 */
	protected $filter_prefix = 'pw';
 
	/**
	 * Directory name where custom templates for this plugin should be found in the theme.
	 *
	 * @since 1.0.0
	 * @type string
	 */
	protected $theme_template_directory = 'pw-templates';
 
	/**
	 * Reference to the root directory path of this plugin.
	 *
	 * @since 1.0.0
	 * @type string
	 */
	protected $plugin_directory = PW_SAMPLE_PLUGIN_DIR;
 
}

This class simply defines the prefix for filters, the name of the directory that files will be searched for in from the current theme, and also the plugin’s own directory.

Third, you include both of these class files into your plugin and instantiate the sub class:

<?php
 
define( 'PW_SAMPLE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
 
require PW_SAMPLE_PLUGIN_DIR . 'class-gamajo-template-loader.php';
require PW_SAMPLE_PLUGIN_DIR . 'class-pw-template-loader.php';
 
function pw_sample_shortcode() {
 
	$templates = new PW_Template_Loader;
 
	// Templates will be loaded here
 
}
add_shortcode( 'pw_sample', 'pw_sample_shortcode' );

Note, I’m using a simple short code here just to help illustrate how this works. It does not have to be in a short code.

Fourth, create a folder called templates in your plugin’s directory and add your template files to that directory.

Now you can load template files like this:

$templates->get_template_part( $slug, $name );

When put in our short code, it looks like this:

function pw_sample_shortcode() {
 
	$templates = new PW_Template_Loader;
 
	ob_start();
	$templates->get_template_part( 'content', 'header' );
	$templates->get_template_part( 'content', 'middle' );
	$templates->get_template_part( 'content', 'footer' );
	return ob_get_clean();
 
}
add_shortcode( 'pw_sample', 'pw_sample_shortcode' );

Note that an output buffer is used because short codes, in order to work correctly, must always return their content, and the template loader includes the files directly.

To help demonstrate exactly how to use this class, I’ve written a sample plugin that can be viewed and downloaded from Github.

Having template files available for advanced users of your plugin can dramatically improve the flexibility of your plugin. I would absolutely recommend implementing them in any and all decently large plugins that include output on the front end of the site.

  1. Danny Jones

    Fantastic tutorial

    Thanks for sharing your expertise pal

  2. Eric Daams

    Great to see there’s an easy class to extend for this, so hats off to Gary Jones.

    One thing I like to do as well is make the theme template directory a filterable value. This allows themers to organize all their plugin template overrides in a single, logical place. For example, if your theme is providing support both for WooCommerce and BBPress, you can organize the templates for both inside a templates directory. IE: templates/woocommerce and templates/bbpress

    Cheers,
    Eric

    • Gary Jones

      Eric,

      That’s already possible, as part of the existing library. You can filter the array in {filter_prefix}_template_paths to include your own prioritised list of directories to search.

      Let’s say you’re using the sample plugin outlines in this tutorial. The plugin author has decided that it should look in:
      * /themes/child-theme/pw-templates (priority 1)
      * /themes/parent-theme/pw-templates (priority 10)
      * /plugins/pw-sample/templates (priority 100).

      Since you can filter that array, you could add extra paths in, or even unset the existing ones, so you can define a single place to look e.g. /themes/child-theme/templates/pw-sample.

  3. Nate Wright

    Thanks for the heads-up on Gary’s class. It’s nice to see a common, shared solution emerge.

    One thing I’ve struggled with is how to best expose a plugin’s custom template structure to the end user – particularly the novice dabbler who may not know where to look to find and copy the template files from the plugin. Some large plugins automatically copy the template files from the plugin folder to the active theme folder. Personally, I dislike a plugin tampering with my theme folder, so I would avoid this approach. Has anyone else come up with a good solution to this problem?

    • Pippin

      I don’t see any reason why you could have the user put the files in a folder in wp-content/. All you’d have to do is adjust the locations the template loader looks in.

    • Nate Wright

      I guess my question was more about how to make the user aware of the files so that they can locate them to copy and edit in their own theme. I guess that’s what the Plugins > Editor window is for. I’ve never used the editor panels myself, but maybe the novice users I am worried about make greater use of them.

    • Pippin

      I think that’s just a matter of documentation.

    • Gary Jones

      You wouldn’t want a theme customiser to be using the plugin editor on your plugin. That’s not going to help anyone (it’s evil anyway).

      I agree that it’s a matter of documentation. How would you know that WooCommerce or EDD supports templates in a theme? Because their installation / developer documentation covers how to do it.

    • Nate Wright

      Sure, documentation is important. I was just wondering if there was a good way to push novice-level information like this into the native WordPress admin experience. But I guess you can only hold their hands so much. A help section under the plugin options is probably the best we can do.

  4. Mark Root-Wiley

    Nice write up! Good to know about the class to. I’ll check that out.

    I’m curious about one thing. In `rcp_locate_template()` why not just use `locate_template()` which runs those same checks in that order in one function call? I can’t think of a situation when a plugin developer would need to know if a template override is coming from a child theme or a not-child theme.

    • Pippin

      By using my own function I can add additional locations for it to look in, as well as add in extra hooks specific to my plugin.

    • Gary Jones

      Mark,

      Whilst doing my class, I did look at whether some of the methods could better re-use core functions by calling them, instead of duplicating them. I tried (and failed to get working) something along the lines of: “Did locate_template() return anything? If so, use it. If not, try looking in the fallback directory of the plugin instead.”. With more time and eyes, maybe it would work, but duplicating the approach of core functions works for now, instead of coming up with wrapper code.

    • Gary Jones

      Forgot to add, that if locate_template() had a contextual filter inside of it, so we could directly change where WP looked for template files, then a couple of the methods of my class could be removed in favour of a simple array filter, making the class mostly redundant.

    • Mark Root-Wiley

      Thanks for the replies. All helpful.

      FWIW, here’s a bit of generalized code I’m using in one plugin of mine with `locate_template` to allow templating of a widget’s output:

      `$template = locate_template( ‘my_plugin_template_folder/layout.php’, false, true );

      // Allow themes to override template
      if( $template ) {

      require( $template );

      } else {

      require( plugin_dir_path( dirname(__FILE__) ) . ‘my_plugin_template_folder/layout..php’ );

      }`

      As far as I know, it’s worked for those who need it, but you’re both right that it’s not filterable.

  5. Chris ODell

    I have done something similar in a plugin I have recently released called the Pico Plugin Framework. I do not like the way that HTML is so often mixed in with other logic in many plugins I see on WordPress.org. In the Pico Plugin framework I have called the ‘templates’ views but the concept is exactly the same really. Separating the presentation code from the rest of the logic in the plugin.

    • Gary Jones

      Do you have a link to your Pico Plugin Framework Chris?

  6. Karl Bowden

    HI All,

    I’ve recently bee updating a plugin to use this same template technique too. Wonderful to see it getting more coverage.

    What are your thoughts on also providing content available to the templates via an extract() wrapper? (Ie: https://github.com/agentk/issuem/blob/master/issuem-templates.php : issuem_process_render(); )

    It then allows clean abstraction using a view class structure, (Ie: https://github.com/agentk/issuem/blob/master/view-classes/issuem-class-article.php)

    No more HTML in the code.

  7. paul

    Greta tutorial thanks

  8. Daniel

    I’m trying to load templates for custom post types from a plugin. That’s not what you’re covering here is it? If I understand correctly this is more for templates used to format output from shortcodes and widgets. I see how you use it to do that. But it’s the login page example that’s confusing me. I have for example archive-type.php and single-type.php I want to include the templates from my plugin if they don’t already exist in the theme. Can I do that with this system?

  9. Nick Fox

    I think there is a serious problem with this tutorial and it has to do with the Gamajo_Template_Loader class. Your tutorials are very popular and so people are doing what you suggest above and extending that class in their plugin projects. The problem is that it is causing namespace clashes with other plugins that are extending that class.

    As a new plugin developer, I learned that hard way when creating my plugin.

    https://wordpress.org/plugins/gps-tracker/

    What I had to do was to rename that class and then everything worked fine. I don’t know if that’s the best way to solve to problem but please mention this in your tutorial. It caused quite a bit of gnashing of teeth while I was trying to troubleshoot this problem.

    thanks
    Nick

    • Pippin

      Hey Nick!

      To prevent that kind of conflict, you just need to include a class_exists() check in your plugin prior to loading the template file class. That way if another plugin has already loaded it, you don’t attempt to load it again.

  10. Sal

    Thank you, Pippin! Everything works great. As usual, you are most helpful.

Error: Please enter a valid email address

Error: Invalid email