How do I Make a Theme “plugin-ready”?

After working on several projects so big I didn’t even know all the people involved, I came to one conclusion:

Keep it simple, write great documentation.

Code is simple if it is easy to read, to learn and to extend.

Do not reinvent the wheel: Use the given hooks wherever possible, add new ones in a predictable scheme.

A very basic example:

if ( ! is_singular() && is_active_sidebar( 't5-archive-sidebar' ) )
{
    do_action( 'sidebar_before' );
    print '<ul id="sidebar">';
    dynamic_sidebar( 't5-archive-sidebar' );
    print '</ul>';
    do_action( 'sidebar_after' );
}

By looking at the id attribute anyone can predict the hooks, because the are named always the same. You know already how the hooks for <div id="header"> and <div id="content"> will be named. There is an interesting Trac ticket you should read and the Theme Hook Alliance @Otto recommended in his answer.

Register all callbacks for your hooks in one place: the start of the functions.php. If everything is bound to a hook you don’t need function_exists(), because a plugin or a child theme can just unregister your function and use its own instead.

Example:

add_action( 'content_before',       't5_frontpage_widget' );
add_action( 'footer_before',        't5_loop_navigation' );
add_action( 'header_before',        't5_skiplink', 1, 0 );
add_filter( 'the_title',            't5_fill_empty_title', 20, 1 );
add_action( 'wp_loaded',            't5_post_format_support' );
add_action( 'wp_loaded',            't5_load_theme_language' );
add_action( 'wp_loaded',            't5_setup_custom_background' );
add_action( 'wp_loaded',            't5_setup_custom_header' );
add_action( 'wp_loaded',            't5_setup_sidebars' );
add_filter( 'wp_title',             't5_wp_title_filter', 20, 2 );

Include additional files as late as possible, make it easy to replace those files, and use one file per class.

Use PHPDoc for all functions and classes, add the hook each one is called.

Example:

/**
 * Handles posts without a title. Uses the first 35 caharacters instead.
 *
 * @wp-hook the_title 20
 * @param  string $title
 * @return string
 */
function t5_fill_empty_title( $title )
{
}

The @wp-hook the_title 20 tells the reader exactly when that function will be called and how to remove it. For complex code provide usage examples in the DocBlock.

Avoid code that makes plugin code hard to write:

  • Never include files, declare functions or create global variables in view files (templates). Child theme authors would have to recreate those again – waste of time.
  • Never just run code when the functions.php is called. Bind everything to a hook to give plugins a chance to disable the code.
  • Never use a priority 0.
  • Never use require, require_once or include and include_once in your theme. Use locate_template() instead. In some cases a plugin might register its own directory as an additional theme directory for a child theme. locate_template() allows such a plugin to replace a complete file.
  • Never create anonymous objects.
  • Never put custom post types, custom taxonomies, shortcodes or contact forms in a theme. That is plain plugin territory.

And last but not least: Use version control (Git, Mercurial), write atomic commits, and explain in each commit message why you made this change.

Leave a Comment