WordPress Plugin Development Best Practices: Template Files

#
By Iain Poulson, Product Manager

When we talk about templates in WordPress we are normally referring to page template files in the theme. However, there are plugins that use template files to display content, and that becomes another consideration when it comes to building WordPress themes.

Can you override plugin template files? If so, how? If you are building a plugin that renders HTML, how can you make it easily altered by themes? I’ll answer these questions in this article.

What Are Template Files?

Before we get too deep, let’s look at the differences between a plugin template and template files loaded by a plugin. A plugin template, sometimes called a plugin boilerplate, is a base to help you build plugins more quickly. Plugin templates take care of some of the manual labor involved in creating a plugin, such as renaming classes.

Template files loaded by a plugin, on the other hand, are PHP files that also contain HTML and template tags. Template files are loaded in a specific manner, governed by the WordPress template hierarchy. WordPress looks for specific template files in the theme, and fallbacks are used if they don’t exist.

For example, say a car sales site has registered a custom post type of ‘car’. When a user visits the page for a specific car, WordPress looks in the theme for a template file called single-car.php. If it doesn’t exist, it falls back to the single.php template for displaying generic posts.

A theme isn’t required to have these templates, but the more templates it has, the more granular control the theme has over how different content is displayed. As the Templates Files section of the Theme Handbook notes:

The most critical template file is the index, which is the catch-all template if a more specific template can not be found in the template hierarchy. Although a theme only needs an index template, typically themes include numerous templates to display different content types and contexts.

WordPress Plugin Template Files

WordPress and themes use template files to determine how a site is displayed. In the same way, if a plugin needs to output HTML to the frontend, it’s a good idea to use a template file.

It’s good practice to separate markup from your code so you don’t end up writing functions that do lots of print() and printf() just to render HTML, or have large HTML blocks inside functions.

It helps to organize your plugin files and, although it’s quite a way off from MVC architecture (you’d need a WordPress framework for that), it does mean your ‘view’ templates can be extended and overridden. If you leave your plugin files unorganized, you’ll find yourself peppering your code with lots of do_action and apply_filters calls to make the output customizable.

Not all plugins will need to do this, but those that output HTML to the site, such as ecommerce or membership plugins, should allow the theme to override them.

There are many plugins that use templates and allow developers to override them. WooCommerce, Easy Digital Downloads, and WP User Manager all have this feature, but each implement it in a different way. Let’s take a look at how they do it.

Overriding Plugin Templates

If a plugin allows template customization, the general convention is that the template files should be copied from the plugin to a theme directory with the name of the plugin slug, maintaining any subdirectory path from the plugin.

For example, wp-content/woocommerce/templates/checkout/form-checkout.php would be copied to wp-content/theme/yourthemename/woocommerce/checkout/form-checkout.php. At that point, the HTML and PHP in the copied file can be tweaked as required.

However, Easy Digital Downloads looks for customized templates in a directory called EDD_Templates in the theme root directory. This name can be altered using the edd_templates_dir filter:

add_filter( 'edd_templates_dir', function() { return 'easy-digital-downloads’' } );

If you’re customizing the templates of another plugin, it’s always a good idea to check their documentation to see where the template files should be copied to in the theme.

Implementing a Template Hierarchy in Your Own Plugin

Let’s take a look at how each plugin implements template loading. This will help you decide which approach to use in your plugin.

The WordPress Way

WooCommerce uses the wc_get_template_part function to load its templates, which in turn calls the native WordPress function locate_template. This is where the magic happens. The function looks for the template file in three locations: the child theme (if applicable), the parent theme, and the wp-includes/theme-compatdirectory in the WordPress root. The function then loads the template if found.

If locate_template comes up empty, then WooCommerce loads its version of the template. The function does allow changing of the template using the wc_get_template_part filter, so other plugins can also alter core WooCommerce templates.

Roll Your Own

Easy Digital Downloads takes a similar approach, but doesn’t use the WordPress function locate_template to find the template within the theme files. Its own edd_get_template_part calls a function stack that grabs an array of directories to search in. This includes the same locations: child theme, parent theme, and EDD’s own plugin template directory. Again, these paths can be filtered using the edd_template_paths filter. The function won’t look in the wp-includes/theme-compat directory. Doing it this way allows greater flexibility for where the templates are stored, as it expands the locations where the plugin will look for templates.

Once found, the template is loaded using the WordPress native function load_template, which is what locate_template uses as well.

Third-Party Library

Instead of writing its own function to load the template, WP User Manager uses the Gamajo Template Loader library to do the work for it. In the interest of full disclosure, I should note that I own and maintain this plugin.

The get_template_part() function in WordPress was never really designed with plugins in mind, since it relies on locate_template() which only checks child and parent themes. So we can add in a final fallback that uses the templates in the plugin, we have to use a custom locate_template() function, and a custom get_template_part() function. The solution here just wraps them up as a class for convenience.

The plugin then implements the library by creating a class that extends the Gamajo_Template_Loader class, setting some plugin specific variables.

<?php
class WPUM_Template_Loader extends Gamajo_Template_Loader {
    /**
     * Prefix for filter names.
     *
     * @var string
     */
    protected $filter_prefix = 'wpum';

    /**
     * Directory name where templates should be found into the theme.
     *
     * @var string
     */
    protected $theme_template_directory = 'wpum';

    /**
     * Current plugin's root directory.
     *
     * @var string
     */
    protected $plugin_directory = WPUM_PLUGIN_DIR;

    /**
     * Directory name of where the templates are stored into the plugin.
     *
     * @var string
     */
    protected $plugin_template_directory = 'templates';

}

This class is then instantiated and is used whenever a template is loaded in the plugin code. For example:

<?php
$templates = new WPUM_Template_Loader();
$active_tab = wpum_get_active_profile_tab();
$data = array( 'user' => $user );
$templates->set_template_data( $data )->get_template_part( "profiles/{$active_tab}" );

In the example above, you can see the use of the set_template_data method to enable the calling code to pass data to the template. This is a powerful feature that both the previous implementation techniques lack. It allows passing data to the template by setting a new item to the $wp_query->query_vars array, which are then available to be called in the child template:

<?php
$cover_image = get_user_meta( $data->user->ID, 'user_cover', true ); ?>
<div id="header-cover-image" style="background-image: url(<?php echo esc_url( $cover_image ); ?>);">
    <div id="header-avatar-container">
        <a href="<?php echo esc_url( wpum_get_profile_url( $data->user ) ); ?>">
            <?php echo get_avatar( $data->user->ID, 128 ); ?>
        </a>
    </div>
</div>

The other plugins would have to manually set this data to $wp_query, each time they do it, or they might end up including more function calls inside the template file.

As plugin developers, using libraries or packages means we don’t have to write the same code again and again, across multiple plugins, and we can benefit from the expertise of others who have written the library, contributed to it, and battle tested it.

There is a downside to using PHP libraries in a WordPress plugin. You could end up in dependency hell, where your plugin could end up using a different version of the library that has been loaded by another plugin. WP User Manager uses Composer to include the library to make it easy to update the package. However it doesn’t, as yet, safeguard from dependency issues so I’ll be using this guide for avoiding namespace issues with Composer packages in the next major plugin release.

Going the Extra Mile

What happens if a plugin makes changes to their template files, and your theme now has an outdated version that you have customized? Sure, the change might be some innocuous tweaks to HTML or class names, but often there’s a change to functionality that you don’t want to miss. Missing critical changes to templates can happen, and typically you would only spot this if you saw an issue when testing your site after updating the plugin. Not all plugins receive the same rigorous testing after an update by site owners.

WooCommerce has identified this as a critical problem for users and created a system to protect against it. As part of the “Status” page, they show which templates have been customized by the theme, as well as indicating if the version is out of date and needs an update.

WooCommerce status panel for template files.

This is made possible because each of their template files have a version number in the file header which can be compared against the theme templates. The version is only incremented when a change is made to the template:

<?php
/**
 * Checkout Form
 *
 * @author      WooThemes
 * @package WooCommerce/Templates
 * @version     3.4.0
 */

In my opinion, this kind of functionality should exist in core, or at least be packaged up in such a way that other plugins can make use of it. Changes to plugin templates that aren’t replicated in the theme templates can result in breaking changes for the site. These can have serious consequences if they’re not caught, especially for ecommerce sites.

Wrapping Up

I hope that’s been a helpful guide to the best practice of including template files within plugins, showing you how to customize them and build in the same functionality in your own plugins.

About the Author

Iain Poulson Product Manager

Iain is a product manager based in the south of England. He also runs multiple WordPress products. He helps people buy and sell WordPress businesses and writes a monthly newsletter about WordPress trends.