The Right Way to Include a Plugin in a WordPress Theme

In our FavePersonal theme we integrated the excellent Social plugin from MailChimp and included an option to turn it on or off. Most importantly we did it in a way that still allows the Social plugin to be installed and updated independently. We made sure that we implemented this in a manner that:

  1. respected users’ expectations
  2. allowed users to manage the plugin independently of the theme

I thought it might be useful to outline the approach we used in a little detail in case others might want to do something similar.

At Crowd Favorite we believe strongly in the principle of least astonishment. WordPress users expect to be able to download and install plugins whenever they see something that looks interesting. We wanted to make sure that we didn’t impede this process. So even though FavePersonal includes the Social plugin, we wanted to make sure we accounted for situations where Social was already installed on a site before FavePersonal was activated; as well as handling a situation where someone installs and activates the Social plugin once FavePersonal is already installed.1

Foundation

When including a plugin in a theme, the main thing you want to avoid is a situation where the same PHP code is processed twice. This will trigger a fatal error (often times resulting in the white screen of death). In other words, don’t just include() the plugin in your theme. If someone already has that plugin activated on their site, you’d have the “loaded twice” issue (and your site would crash).

To avoid this situation, it’s useful to look at the WordPress load process (in wp-settings.php, referencing WP 3.4.1). The first thing WordPress does is load in its core functionality:

// Load most of WordPress.
require( ABSPATH . WPINC . '/class-wp-walker.php' );
require( ABSPATH . WPINC . '/class-wp-ajax-response.php' );
require( ABSPATH . WPINC . '/formatting.php' );
require( ABSPATH . WPINC . '/capabilities.php' );
require( ABSPATH . WPINC . '/query.php' );
require( ABSPATH . WPINC . '/theme.php' );
[...]

This is followed by the loading of MU (Must Use) plugins; these are plugins that are placed into the wp-content/mu-plugins directory and cannot be managed from the Plugins screen.

// Load must-use plugins.
foreach ( wp_get_mu_plugins() as $mu_plugin ) {
    include_once( $mu_plugin );
}
unset( $mu_plugin );

You’ll see the standard plugins loaded on line 196:

// Load active plugins.
foreach ( wp_get_active_and_valid_plugins() as $plugin )
    include_once( $plugin );
unset( $plugin );

and on line 282 you’ll see the theme’s functions.php file loaded, along with any parent theme (if your theme is a child theme):

// Load the functions for the active theme, for both parent and child theme if applicable.
if ( ! defined( 'WP_INSTALLING' ) || 'wp-activate.php' === $pagenow ) {
    if ( TEMPLATEPATH !== STYLESHEETPATH && file_exists( STYLESHEETPATH . '/functions.php' ) )
        include( STYLESHEETPATH . '/functions.php' );
    if ( file_exists( TEMPLATEPATH . '/functions.php' ) )
        include( TEMPLATEPATH . '/functions.php' );
}

Right after that, you’ll see the following action:

do_action( 'after_setup_theme' );

Ok, now we have all the bits of the process we need to understand to properly conditionally include a plugin from a theme. So let’s do it.

Implementation

Since the theme is loading in last, we can use that to our advantage. If the plugin has been installed separately as a plugin (and not as part of the theme), we will be able to see that it is active. If it is already active, we don’t want to load it again a second time.


<?php
// Run this code on 'after_theme_setup', when plugins have already been loaded.
add_action('after_setup_theme', 'my_load_plugin');
// This function loads the plugin.
function my_load_plugin() {
// Check to see if your plugin has already been loaded. This can be done in several
// ways – here are a few examples:
//
// Check for a class:
// if (!class_exists('MyPluginClass')) {
//
// Check for a function:
// if (!function_exists('my_plugin_function_name')) {
//
// Check for a constant:
// if (!defined('MY_PLUGIN_CONSTANT')) {
if (!class_exists('Social')) {
// load Social if not already loaded
include_once(TEMPLATEPATH.'plugins/my-plugin/my-plugin.php');
}
}

In our FavePersonal theme we added an additional check in the if statement to see if the setting to activate the Social plugin was turned on. This allows us to avoid loading the plugin if the site owner has decided to disable this functionality.

There you have it – very simple, the plugin will only be loaded by the theme if it hasn’t been activated separately as a plugin, and the plugin is properly in place before init or wp_loaded (where plugins commonly do set-up work).


  1. Or perhaps a situation where someone: activates the FavePersonal theme, finds that they like the features of Social, changes to a new theme and installs/activates the Social plugin, then switches back to the FavePersonal theme (while the Social plugin is still active). 

This post is part of the following projects: Personal Theme, Social. View the project timelines for more context on this post.