Theme-ready Custom Post Types in WordPress

If you’ve ever built a large, custom site in WordPress, you’ve probably built a few custom post types.

They are fantastic ways to manage custom content in an application traditionally geared towards blog and news posts.  You can manage events, downloadable publications, music archives. CPTs are only limited by the bounds of your imagination.

Many developers are building themes that leverage this custom content.  Unfortunately, they’re bundling the CPT definitions directly in the theme.  Let me tell you something about that:

DON’T DO IT!

Site owners and users change their themes often to keep a fresh look on the site.  If a custom post type is defined by the theme, and the user changes the theme, the user effectively loses access to that content.

Instead, define your custom post types in a plugin and reference them in the theme.

Require A Dependency

First, build a plugin to register your custom post type.

Second, build a theme to manage the presentation of this custom data.

There is a very loose coupling of functionality here.  If the theme is disabled and replaced, the custom post types remain in the database and accessible to the user through the WordPress admin interface.  If the plugin is disabled, the theme continues to work, but CPT-specific files like single page templates are never loaded.

Basically, you can use one without the other with zero negative repercussions for the end user.

But what if you absolutely need the theme and the plugin to both be installed?  In that case, place a dependency in one on the other.

My Stacked theme for WordPress was a custom client project that includes such a dependency.  Yes, you can use the plugin separate from the theme, but it made no sense from the client’s perspective to ever  use the theme without the plugin.

So I made the theme depend on the plugin.

The theme applies a filter to a Boolean value (false) and checks to see whether or not it returns true.  If it does, then things are golden.  If not, it alerts the user that they’re missing a dependency.

1
2
3
4
5
6
7
8
9
10
if ( ! apply_filters( 'stacked-installed', false ) )
    add_action( 'admin_notices', array('Stacked_Theme', 'plugin_not_installed') );

class Stacked_Theme {
    public static function plugin_not_installed() {
        echo '<div id="message" class="error">
            <p>This theme requires the Stacked Plugin for proper operation.  Please install it to ensure proper functionality!</p>
        </div>'
;
    }
}

The plugin simply adds a filter to stacked-installed that returns true to inform the theme that it’s installed, active, and available:

1
add_filter( 'stacked-installed', '__return_true' );

The theme and plugin still function independently, and nothing breaks for the user if they disable one or the other, but if they do they’re informed that certain expected functionality will be disabled.

Lock-in the Template

In certain, very rare cases, a developer might need to include the custom post type template directly in the plugin. This makes sense only if the CPT’s display is 100% independent of the rest of the site. If the custom template includes the default WordPress header, footer, sidebars, or other template parts, you end up with some strange behavior.

For example, your custom template might assume 1 sidebar but be used on a site styled for 2 sidebars. Or no sidebars at all. Isolate the template from the theme in its entirety or not at all.

WordPress conveniently provides a filter so we can specify the location of our locked-in template. The {post_type}_template filter allows us to hook in and override whatever is set by the theme. A simple function in our plugin can now hijack the regular templating system, load our custom post type’s template, and leave the rest of the site unaffected.

Assume our custom post type is registered as "jdm_custom":

1
2
3
4
5
6
7
function jdm_custom_template_override( $path ) {
    if ( locate_template( 'single.php' ) == $path ) {
        $path = dirname( __FILE__ ) . '/jdm_custom-single.php';
    }
    return $path;
}
add_filter( 'jdm_custom_template', 'jdm_custom_template_override' );

If there is no custom single template for our post type defined, WordPress will assume we want to use single.php and will get a reference to it using locate_template(). If there is a custom template defined, WordPress will use that instead. We need to check first whether or not WordPress has loaded a custom template before forcing our own.

This allows site owners to define their own version of jdm_custom-single.php in the theme and not use the one provided by our plugin. Not a requirement, but a fair option.

If no custom template exists, we find a reference to ours and pass it back to WordPress instead. Now, WordPress loads our template separate from the site’s theme and we have complete control over presentation through our plugin.

About Eric

Eric Mann is a writer, web developer, and outdoorsman living in the Pacific Northwest. If he's not working with new technologies in web development, you can probably find him out running, climbing, or hiking with his dog.

Comments

  1. Nice article, Eric. This is the first time I’ve seen the option to override the templating system.

    What I’ve done in some of my custom plugins, for thinks like audio post types, is filter the content and insert the new content and replace it totally, rather than rely on the theme to do so. I don’t love this method, but it works. Most of the time when I do this it’s because I carry a lot of meta data with custom display or something like an audio player.

    Either way, nice article, and I’m glad you’re spreading the word that post types belong in plugins : )

  2. We did something like this with the Agentpress theme. :) It has a Listings plugin as well. Take that plugin, snag the extra templates, and you can pop it in another child theme for Genesis.

  3. Good food for thought. I’ve been involved in a few projects recently where we implemented custom post-types into custom themes. I was unsure if that was the best approach at the time, but am now swaying towards implementing them into plugins now.

Trackbacks

Leave a Reply