A few weeks ago, I gave a presentation at WordCamp Kansas City titled “Modular Plugins”. The talk was focused on the idea of writing extensible code when building plugins. I’d like to continue a discussion of the same topic here.

The first question a lot of people will ask is “what exactly is extensible code?”. The answer is quite simple:

Extensible, or modular code, is code that can be modified, interacted with, added to, or manipulated . . . all without ever modifying the core code base.

What does this really mean in the context of WordPress plugins? Well, it means that other developers can modify the behavior of your plugins from within their own plugins or themes. One example that you might see quite often is a theme that includes “compatibility” code for popular plugins. Sometimes this means that themes will include options to automatically remove meta boxes added by plugins that conflict or are unnecessary in the theme. Sometimes this means that themes (or other plugins) will include additional options if a particular plugin, such as WordPress SEO, is active, and they will be options specific to that plugin. It could also mean that the theme (or plugin) removes options or interface elements from the plugin.

Going beyond the idea of themes or plugins using extensible code for compatibility reasons, one of the largest reasons for extensible code is so that you can provide other developers ways to extend your code base to do more than what you wrote.

Imagine how cool it would be to know that other developers could write add-on plugins for your original plugin, all without you ever having to do any maintenance or merging of code bases.

That’s exactly the idea behind modular plugins.

Take my Easy Digital Downloads plugin for example. While the code base for the plugin is quite large, the scope of the plugin is probably double the original code because it has very modular code, allowing myself and other developers to write add-ons for it.

At this point, EDD has 20 published add-on plugins that each rely on Easy Digital Downloads to function but remain a completely separate entity.

But wait, there is more. Writing extensible code isn’t just about allowing other developers to add/remove portions of your code for compatibility reasons, or for allowing other developers to write add-on modules. There is another reason and it is perhaps the most important of all:

Good extensible code makes it exceptionally easy to expand your own code base.

Have you ever written a plugin and then later on discovered that you wanted to expand it, but then ran into difficulties because your code base wasn’t flexible? A lot of times what will happen in these kinds of cases is you will end up with functions that call a dozen or more other functions, which means that if a single one of these functions breaks, or fails for whatever reason, it is quite likely that your entire set of functions will fail.

When we use extensible code to write our WordPress plugins, it is extremely easy to add in additional functionality, and to do so without much risk to our existing code base.

If you don’t understand this last reason, it should be more clear once I get into the code samples.

Since code doesn’t just magically become extensible (except in some cases of OOP), we, as developers, have to consciously make the choice to write our code in an extensible manner.

If you have not already made the jump to incorporating modular code into your WordPress plugins, then I would strongly encourage you to do so. Believe me, you will thank me later.

I have found that even when you do not have a particular scenario in mind where your extensible code will be taken advantage of, it is still best to do, even if for only your own use.

At this point, if you’re asking the question “how do I write extensible code?”, then just read on as I’m going to give some examples.

Keys to Building Modular Plugins

There are several very important functions from WordPress core that make writing extensible code exceptionally easy:

  • add_filter() – allows you to attach data manipulation functions to existing “hooks”
  • apply_filters() – allows you to specify a “hook” that is tied into with add_filter()
  • has_filter() – allows you to check if a specified filter exists
  • add_action() – allows you to attach a function for execution to an existing “hook”
  • do_action() – allows you to to specify a “hook” that executes all attached functions via add_action()
  • has_action – allows you to check if a specified action has been registered

If you have an understanding of how these six functions work, then you already know everything you need to write modular WordPress plugins.

I’m going to walk you through some examples now from Easy Digital Downloads.

The plugin uses custom post types for the digital products, and one of the functions Matt and I built into the plugin is a function that retrieves the default labels for singular and plural items. Here is the function:

1
2
3
4
5
6
7
function edd_get_default_labels() {
	$defaults = array(
	   'singular' => __('Download','edd'),
	   'plural' => __('Downloads','edd')
	);
	return apply_filters( 'edd_default_downloads_name', $defaults );
}

Notice that the labels are setup in an array and then passed through a filter called “edd_default_downloads_name”. The filter is applied using apply_filters().

What happens with apply_filters() here? Well, by default absolutely nothing, but it allows users to easily modify the labels used throughout the entire plugin. For example, if a user wanted to change “Download” to “Product”, and “Downloads” to “Products”, all they’d have to do is this:

1
2
3
4
5
6
7
8
9
<?php
function pw_edd_global_labels($labels) {
	$labels = array(
	   'singular' => __('Product','edd'),
	   'plural' => __('Products','edd')
	);
	return $labels;
}
add_filter('edd_default_downloads_name', 'pw_edd_global_labels');

With this filter present, the labels used to reference the post type have been changed through the entire plugin interface, including all admin pages, such as Payment History and Reports, as well as all widget names and titles displayed on the front end.

Let’s look at an example of add_action() and do_action() now.

When going through the checkout process of Easy Digital Downloads, there comes a point where you need to enter payment information. For some gateways this is credit card info, and in other gateways this is nothing more than an email address. Because different payment gateways have different requirements, this has to be accounted for and allowed. The solution is quite simple:

1
2
3
4
5
6
7
8
9
// load the credit card form and allow gateways to load their own if they wish
 
// $payment_mode will be the name of the gateway, such as "paypal".
 
if(has_action('edd_' . $payment_mode . '_cc_form')) {
	do_action('edd_' . $payment_mode . '_cc_form'); 
} else {
	do_action('edd_cc_form');
}

This is the snippet used for loading the credit card form during the checkout process. If the payment gateway has registered an action called “edd_{gateway ID}_cc_form” then the attached function (which outputs the CC form) is executed. If no matching action is found, the default credit card form is displayed. The presence of this action check makes the process of creating custom payment gateways not only simpler but also less likely to fail.

When a payment gateway uses a custom credit card form, the following function (in some form or other) will be setup:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// setup a custom CC form for Stripe
function edd_stripe_cc_form() {
	ob_start(); ?>
	<?php do_action('edd_before_stripe_cc_fields'); ?>
	<fieldset>
		<p>
			<input type="text" autocomplete="off" name="card_name" class="card-name edd-input required" />
			<label class="edd-label"><?php _e('Name on the Card', 'edd'); ?></label>
		</p>
		<p>
			<input type="text" autocomplete="off" name="card_number" class="card-number edd-input required" />
			<label class="edd-label"><?php _e('Card Number', 'edd'); ?></label>
		</p>
		<p>
			<input type="text" size="4" autocomplete="off" name="card_cvc" class="card-cvc edd-input required" />
			<label class="edd-label"><?php _e('CVC', 'edd'); ?></label>
		</p>
		<?php do_action('edd_before_stripe_cc_expiration'); ?>
		<p class="card-expiration">
			<input type="text" size="2" name="card_exp_month" class="card-expiry-month edd-input required"/>
			<span class="exp-divider"> / </span>
			<input type="text" size="4" name="card_exp_year" class="card-expiry-year edd-input required"/>
			<label class="edd-label"><?php _e('Expiration (MM/YYYY)', 'edd'); ?></label>
		</p>
	</fieldset>
	<?php do_action('edd_after_stripe_cc_fields'); ?>
	<?php
	echo ob_get_clean();
}
add_action('edd_stripe_cc_form', 'edd_stripe_cc_form');

Let’s look now at one more example. In Easy Digital Downloads, items can be added to the shopping cart, just like most ecommerce plugins. Wouldn’t it be really cool if we had a really simple way to attach custom functions to the main function that adds an item to the cart? Perhaps we want to record some tracking info anytime products are placed in the cart. Because of the way EDD is structured, this is very, very easy to do.

In Easy Digital Downloads, there is an action called “edd_add_to_cart”, and it fires every time a download is added to the cart, both with ajax enabled or disabled. The function that processes the add to cart action looks like this:

1
2
3
4
5
6
function edd_process_add_to_cart($data) {
	$download_id = $data['download_id'];
	$options = isset($data['edd_options']) ? $data['edd_options'] : array(); 
	$cart = edd_add_to_cart($download_id, $options);
}
add_action('edd_add_to_cart', 'edd_process_add_to_cart');

Notice that it is hooked to “edd_add_to_cart” with add_action(). Because of this structure, we can attach any other function we want to the same action. Here’s a simple example:

1
2
3
4
5
function pw_edd_track_cart_adds($data) {
	$download_id = $data['download_id'];
	pw_custom_function_for_tracking_cart_adds( $download_id );
}
add_action('edd_add_to_cart', 'pw_edd_track_cart_add', 20);

With this function in place, and it being hooked to “edd_add_to_cart”, pw_custom_function_for_tracking_cart_adds() will run everytime an item is added to the cart.

Super simple.

Earlier on I also mentioned that we could use extensible code for removing things from our plugin, so let’s look at a realistic example in Easy Digital Downloads.

For a couple of client sites I have worked on recently, I found it necessary to remove parts of the “Download Configuration” meta box. The client didn’t need the option to change the purchase button color, and leaving it there just added confusion.

Each of the fields in the meta box are added using an action called “edd_meta_box_fields”, which means we can easily remove any field we want. Here’s an example for removing the purchase button color field:

1
2
3
4
function pw_remove_product_options() {
	remove_action('edd_meta_box_fields', 'edd_render_button_color', 50);	
}
add_action('admin_init', 'pw_remove_product_options');

The plugin still acts exactly the same (I haven’t broken anything by removing this), but I have cleaned up the interface and made it easier for the client by eliminating an unneeded option.

If at any point during this post you have found yourself unconvinced of the good reasons to write modular code, then I want you to think about something else.

WordPress core is extremely modular and is filled with hundreds of actions and filters. Since you are writing a plugin for the WordPress platform, shouldn’t you strive to write your code in the same manner as the system that your code runs in?

When you look into it, WordPress core is really just made up of lots and lots of little (and big) plugins. Pieces of it can be easily removed and and parts can be easily extended beyond their default functionality.

Did you at any point find yourself wondering what in the world the connection with the image at the top and WordPress is? Well, it’s simple. The image illustrates a city built out of modules, and due to the structure of these modules, more modules can be added at any time. WordPress is exactly the same, and the code base for your plugins should be as well.

Now go write extensible code.

  1. Foxinni

    I’ve definitely taken note of this methodology.

    You say that you have coders extending your code, but it looks like it’s mostly in the form of pasting some snippets in the functions.php. How do you/can we properly manage plugin plugins?

    I must say that every now and again if do come across a WordPress function that does not have any filters or action available to plug into and then I feel utterly miserable to have to hack my way around the problem. ie. Controlling the of themes from within plugins. templating

    • Pippin

      The first thing you need to understand is that there is absolutely no difference, in terms of functionality, between placing code in functions.php and placing the code in a custom plugin. They work exactly the same way.

      When I give an example of how to modify something within a plugin, I usually tell them to put it in functions.php because that is the one file they are familiar with. If I tell a non-developer user to create a new custom plugin and place the code in there, they freak out.

  2. Geoffrey Hoffman

    Pip – Your post is good, your recommendations are clear, and your examples are helpful. Still, I was hoping to see some classes being used so that proper object inheritance patterns can be implemented. After all, when you see `extensible code` it means `OOP` to a good PHP developer, but, sadly, it means `Hey, check out these nifty functions` to a WP coder. WordPress is just about the farthest thing from extensible code on the net. At least you’ve shown some good ways to create plugins that can be more versatile than hard-coded, one-trick ponies.

    • Pippin

      Sorry, your comment got marked as spam.

      The best thing to do (at least in some cases) would be to use a combination of OOP and the extensible WP functions (add_action(), do_action(), etc).

      As everything on this site is WordPress specific, talking purely about WP-specific functions is expected.

      Why do you say WP is extremely non extensible? I’ve had exactly the opposite experience. There is very, very little in WordPress that cannot be manipulated / modified through plugins, and those elements that are still “hard coded” are getting updated.

  3. Jean

    Excellent Pippin, that’s one inspiring post. On the theme side, Genesis is one of those themes/frameworks that I’ve found implements these concepts very well.

    • Pippin

      Thanks, Jean!

  4. Latz

    Great article. Added filters and actions to two of my plugins.

    • Pippin

      That’s what I like to hear!

  5. jgalea

    Not sure this is the best place to post this, but what are your thoughts on licences for extensions? Do you prefer a licence that expires after one year (then ceasing to offer support and updates), or a more liberal one like the Genesis licence (free updates and support for life). Also how do you restrict users from using your plugin on more than one site?

    • Pippin

      If using a license with a plugin, I would never restrict the usage of the plugin. I would offer updates and support for the duration of the license (the same way that Gravity Forms functions).

      You should never restrict a user from using a plugin on more than one site, especially since it violates the GPL licensing.

  6. jgalea

    Another question. When making extensions, how do you handle dependencies with the main plugin. Say for example, how do you prevent an extension being activated when the main plugin is not active? And would you automatically deactivate the dependent extensions on deactivation of the main plugin?

    • Pippin

      There are a couple of ways to do it:

      1. Use the is_plugin_active() function to check for the parent plugin
      2. Use function_exists() or class_exists() to see if one of the functions/classes from the main plugin exists (if it doesn’t, the plugin isn’t active).

      I usually add a notice to the dashboard if the main plugin isn’t active.

  7. jgalea

    Thanks Pippin, understood.

Comments are closed.