WordPress Plugin Development Best Practices: Template Files

https://deliciousbrains.com/wordpress-plugin-development-template-files/

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? In this article I’ll answer these questions.

What Are Templates?

The WordPress templating system is one of the most important pillars of the WordPress architecture. Template files are “PHP files that contain a mixture of HTML, Template Tags, and PHP code.”

Template files are loaded in a specific manner, governed by the WordPress template hierarchy. Specific template files are looked for in the theme, and fallbacks are used if they don’t exist. For example, a car listing site has registered a custom post type of ‘car’. When a user visits a page on the site for a specific car, WordPress will look in the theme for a template file called single-car.php. If it doesn’t exist, it will fallback 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 the different content is displayed:

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

Plugin Templates

In the same way WordPress and themes use template files, if a plugin needs to output HTML to the front-end, then 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 (a plugin I recently acquired) all have this feature but they 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 then there is a general convention that the template files should be copied from the plugin to a theme directory with the name of the plugin slug, maintaining any sub directory 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 then 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. Although this name can be altered using the edd_templates_dir filter:

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

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

Implementing a Template Hierarchy in Your Own Plugin

Let’s take a look at how each plugin implements template loading, which 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-compat directory 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 locate 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.

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.

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 doing 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 Pete’s guide for avoiding namespace issues with Composer packages in a future 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 Settings > 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.

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, which if not caught, could have serious consequences for websites, especially 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.

Have you written plugins with templates that can be overridden? What implementation technique did you use? Any tips I might have missed? Let us know in the comments.

Last updated