Have you ever used a plugin and wished it did something a bit differently? Perhaps you needed something unique that was beyond the scope of the settings page of the plugin.
I have personally encountered this, and I'm betting you have, too. If you're a WordPress plugin developer, most likely some of your users have also encountered this while using your plugin.
Here's a typical scenario: You've finally found that plugin that does everything you need — except for one tiny important thing. There is no setting or option to enable that tiny thing, so you browse the documentation and find that you can't do anything about it. You request the feature in the WordPress plugin's support forum — but no dice. In the end, you uninstall it and continue your search.
Imagine if you were the developer of this plugin. What would you do if a user asked for some particular functionality?
The ideal thing would be to implement it. But if the feature was for a very special use case, then adding it would be impractical. It wouldn't be good to have a plugin setting that only 0.1% of your users would have a use for.
You'd only want to implement features that affect the majority of your users. In reality, 80% of users use 20% of the features (the 80/20 rule). So, make sure that any new feature is highly requested, and that 80% of your users would benefit from it, before implementing it. If you created a setting for every feature that is requested, then your plugin would become complicated and bloated — and nobody wants that.
Your best bet is to make the plugin extensible, code-wise, so that other people can enhance or modify it for their own needs.
In this article, you'll learn about why making your plugin extensible is a good idea. I'll also share a few tips of how I've learned to do this.
What Makes A Plugin Extensible?
In a nutshell, an extensible plugin means that it adheres to the "O" part of the SOLID principles of object-oriented programming — namely, the open/closed principle.
If you're unfamiliar with the open/closed principle, it basically means that other people shouldn't have to edit your code in order to modify something.
Applying this principle to a WordPress plugin, it would mean that a plugin is extensible if it has provisions in it that enable other people to modify its behavior. It's just like how WordPress allows people to "hook" into different areas of WordPress, but at the level of the plugin.
A Typical Example Of A Plugin
Let's see how we can create an extensible plugin, starting with a sample plugin that isn't.
Suppose we have a plugin that generates a sidebar widget that displays the titles of the three latest posts. At the heart of the plugin is a function that simply wraps the titles of those three posts in list tags:
function get_some_post_titles() {
$args = array(
'posts_per_page' => 3,
);
$posts = get_posts( $args );
$output = '';
foreach ( $posts as $post ) {
$output .= '- ' . $post->post_title . '
';
}
$output .= '
';
return $output;
}
While this code works and gets the job done, it isn't quite extensible.
Why? Because the function is set in its own ways, there's no way to change its behavior without modifying the code directly.
What if a user wanted to display more than three posts, or perhaps include links with the posts' titles? There's no way to do that with the code above. The user is stuck with how the plugin works and can nothing to change it.
Including A Hundred Settings Isn't The Answer
There are a number of ways to enhance the plugin above to allow users to customize it.
One such way would be to add a lot of options in the settings, but even that might not satisfy all of the possibilities users would want from the plugin.
What if the user wanted to do any of the following (scenarios we'll revisit later):
- display WooCommerce products or posts from a particular category;
- display the items in a carousel provided by another plugin, instead of as a simple list;
- perform a custom database query, and then use those query's posts in the list.
If we added a hundred settings to our widget, then we would be able to cover the use cases above. But what if one of these scenarios changes, and now the user wants to display only WooCommerce products that are currently in stock? The widget would need even more settings to accommodate this. Pretty soon, we'd have a gazillion settings.
Also, a plugin with a huge list of settings isn't exactly user-friendly. Steer away from this route if possible.
So, how would we go about solving this problem? We'd make the plugin extensible.
Adding Our Own Hooks To Make It Extensible
By studying the plugin's code above, we see a few operations that the main function performs:
- It gets posts using
get_posts
. - It generates a list of post titles.
- It returns the generated list.
If other people were to modify this plugin's behavior, their work would mostly likely involve these three operations. To make our plugin extensible, we would have to add hooks around these to open them up for other developers.
In general, these are good areas to add hooks to a plugin:
- around and within the major processes,
- when building output HTML,
- for altering post or database queries,
- before returning values from a function.
A Typical Example Of An Extensible Plugin
Taking these rules of thumb, we can add the following filters to make our plugin extensible:
- add
myplugin_get_posts_args
for modifying the arguments ofget_posts
, - add
myplugin_get_posts
for overriding the results ofget_posts
, - add
myplugin_list_item
for customizing the generation of a list entry, - add
myplugin_get_some_post_titles
for overriding the returned generated list.
Here's the code again with all of the hooks added in:
function get_some_post_titles() {
$args = array(
'posts_per_page' => 3,
);
// Let other people modify the arguments.
$posts = get_posts( apply_filters( 'myplugin_get_posts_args', $args ) );
// Let other people modify the post array, which will be used for display.
$posts = apply_filters( 'myplugin_get_posts', $posts, $args );
$output = '';
foreach ( $posts as $post ) {
// Let other people modify the list entry.
$output .= '- ' . apply_filters( 'myplugin_list_item', $post->post_title, $post ) . '
';
}
$output .= '
';
// Let other people modify our output list.
return apply_filters( 'myplugin_get_some_post_titles', $output, $args );
}
You can also get the code above in the GitHub archive.
I'm adding a lot of hooks here, which might seem impractical because the sample code is quite simple and small, but it illustrates my point: By adding just four hooks, other developers can now customize the plugin's behavior in all sorts of ways.
Namespacing And Context For Hooks
Before proceeding, note two important things about the hooks we've implemented:
- We're namespacing the hooks with
myplugin_
.
This ensures that the hook's name doesn't conflict with some other plugin's hook. This is just good practice, because if another hook with the same name is called, it could lead to unwanted effects. - We're also passing a reference to
$args
in all of the hooks for context.
I do this so that if others use this filter to change something in the flow of the code, they can use that$args
parameter as a reference to get an idea of why the hook was called, so that they can perform their adjustments accordingly.
The Effects Of Our Hooks
Remember the unique scenarios I talked about earlier? Let's revisit those and see how our hooks have made them possible:
- If the user wants to display WooCommerce products or posts from a particular category, then either they can use the filter
myplugin_get_posts_args
to add their own arguments for when the plugin queries posts, or they can usemyplugin_get_posts
to completely override the posts with their own list. - If the user wants to display the items in a carousel provided by another plugin, instead of as a simple list, then they can override the entire output of the function with
myplugin_get_some_post_titles
, and instead output a carousel from there. - If the user wants to perform a custom database query and then use that query's posts in the list, then, similar to the first scenario, they can use
myplugin_get_posts
to use their own database query and change the post array.
Much better!
A Quick Example Of How To Use Our Filters
Developers can use add_filter
to hook into our filters above (or use add_action
for actions).
Taking our first scenario above, a developer can just do the following to display WooCommerce products using the myplugin_get_posts_args
filter that we created:
add_filter( 'myplugin_get_posts_args', 'show_only_woocommerce_products' );
function show_only_woocommerce_products( $args ) {
$args['post_type'] = 'product';
return $args;
}
We Can Also Use Action Hooks
Aside from using apply_filters
, we can also use do_action
to make our code extensible. The difference between the two is that the first allows others to change a variable, while the latter allows others to execute additional functionality in various parts of our code.
When using actions, we're essentially exposing the plugin's flow to other developers and letting them perform other things in tandem.
It might not be useful in our example (because we are only displaying a shortcode), but it would be helpful in others. For example, given an extensible backup plugin, we could create a plugin that also uploads the backup file to a third-party service such as Dropbox.
"Great! But Why Should I Care About Making My Plugin Extensible?"
Well, if you're still not sold on the idea, here are a few thoughts on why allowing other people to modify your plugin's behavior is a good idea.
It Opens Up the Plugin to More Customization Possibilities
Everyone has different needs. And there's a big chance your plugin won't satisfy all of them, nor can you anticipate them. Opening up your plugin to allow for modifications to key areas of your plugin's behavior can do wonders.
It Allows People to Introduce Modifications Without Touching the Plugin's Code
Other developers won't be forced to change your plugin's files directly. This is a huge benefit because directly modifying a plugin's file is generally bad practice. If the plugin gets updated, then all of your modifications will be wiped.
If we add our own hooks for other people to use, then the plugin's modifications can be put in an external location — say, in another plugin. Done this way, the original plugin won't be touched at all, and it can be freely updated without breaking anything, and all of the modifications in the other plugin would remain intact.
Conclusion
Extensible plugins are really awesome and give us room for a lot of customization possibilities. If you make your plugin extensible, your users and other developers will love you for it.
Take a look at plugins such as WooCommerce, Easy Digital Downloads and ACF. These plugins are extensible, and you can easily tell because numerous other plugins in WordPress' plugins directory add functionality to them. They also provide a wide array of action and filter hooks that modify various aspects of the plugins. The rules of thumb I've enumerated above have come up in my study of them.
Here are a few takeaways to make your plugin extensible:
- Follow the open/closed principle. Other people shouldn't have to edit your code in order to modify something.
-
To make your plugin extensible, add hooks in these places:
- around and within major processes,
- when building the output HTML,
- for altering post or database queries,
- before returning values from a function.
- Namespace your hooks' names with the name of your plugin to prevent naming conflicts.
- Try passing other variables that are related to the hook, so that other people get some context of what's happening in the hook.
- Don't forget to document your plugin's hooks, so that other people can learn of them.
Further Reading
Here are some resources if you want to learn more about extending plugins:
- How to Make Your WordPress Plugin Extensible, GitHub
All of the sample code in this article. - "Useful Tips to Get Started With WordPress Hooks," Thomas Maier, Smashing Magazine
- "How to Create a WordPress Plugin," Daniel Pataki, Smashing Magazine
- "Hooks," Plugin Handbook, WordPress.org