How to Use apply_filters() and do_action() to Create Extensible WordPress Plugins

carl sagan mind blown

How does a single plugin become the basis of a thriving technology ecosystem? Partly by leveraging the extensibility that WordPress’s event-driven Hooks system makes possible.

Doesn’t it seem like some WordPress plugins have all the luck? WooCommerce, Easy Digital Downloads, Gravity Forms: each one of these plugins is an entire industry, with dozens of third-party extensions growing its functionality in every direction imaginable, while piggybacking on the plugin’s unique strengths. Meanwhile, most other plugins—such as the redundant form plugin of the week—are total standalones.

How does a single plugin become the basis of a thriving technology ecosystem? There are lots of answers: being first to market, building a great product, having marketing savvy, and all the rest. But today we’re going to look at a major part of the technical answer: extensibility, through WordPress’s event-driven Hooks system.

Today, we’re going to use a working plugin example to cover the two key functions that make plugin extensibility work: apply_filters() and do_action(). Let’s dive in!

Extensions: Plugins for Plugins

So it’s clear what we’re talking about when we say “extensions,” let’s take an example: Stripe for WooCommerce.

This extension does nothing if you don’t already have WooCommerce. If you do, though, you purchase and install Stripe for WooCommerce as a separate plugin, and it broadens WooCommerce’s functionality so that you can start accepting payments through the Stripe payment gateway.

So you’ve got a plugin improving another plugin in a specific way. That’s an extension. They’re the adverbs of the WordPress tech ecosystem.

How Extensions Work: apply_filters() and do_action()

Extensible WordPress plugins make liberal use of two functions: apply_filters() and do_action(), the two ways of creating WordPress hooks.

The next question: how did WooCommerce do that? After all, it’s easy to imagine writing an e-commerce plugin that “only works with the gateways it works with.” Then if you really want Stripe you have to tear apart WooCommerce, and probably end up creating your own proprietary thing called StripeCommerce, which either becomes a competitor to WooCommerce or just lives on the site of the one client who could afford to pay you for the development and ongoing bugfixes.

It’s a mess to contemplate, but fortunately WooCommerce does something much better. It makes liberal use of two functions: apply_filters() and do_action(). These two functions are the two ways of creating hooks in WordPress, and a foundational part of WordPress’s Hooks system in general.

apply_filters() and do_action() have one simple difference:

  1. apply_filters() is how you hook in filter functions.
  2. do_action() is how you hook in action functions.

Let’s look at a live example to see how they work.

A Working Example: A Plugin and Its Extension

For our example, we’ll use a working “Quote of the Day” plugin, plus an extension that modifies that plugin in specific ways. To follow along and see the full code, download both plugins as a ZIP file.

The Original Plugin: Quote of the Day

This plugin pulls from the API exposed by an external library of quotes, storing the resulting quote as a WordPress transient that refreshes once every 24 hours. Come back to this article anytime and you’ll see that day’s random quote on the topic of “life”—which should be suitably broad.

The default plugin displays itself using a shortcode, and looks as follows:

Life is a series of collisions with the future; it is not the sum of what we have been, but what we yearn to be.

Jose Ortega y Gasset

The Extension: Carl Sagan Quote of the Day

As we know, the problem with most quotes is that they weren’t said by Carl Sagan, the astronomer who made a career out of blowing the collective mind of late-20th-century Earth. Our extension fixes that problem, using a number of tools:

  1. A large header indicating today’s date and that the quote is by Carl Sagan,
  2. Some specific word replacements to make any quote into something Carl Sagan would say,
  3. Original quote authors are crossed out and quotes are now correctly attributed to Carl Sagan, and
  4. Below every quote is a graphic of a Carl Sagan impersonator utterly annihilating your most fundamental grasp on reality, as a multimedia reminder of what Carl Sagan quotes are like.

Here’s the plugin, plus extension, in action:

Carl Sagan Quote of the Day for April 26, 2018:

Life seems, without the mind-boggling perspective of quantum physics, to be a series of collisions with the trillions of atoms that make up the future; it seems, without the mind-boggling perspective of quantum physics, to be not the trillions of atoms that make up the sum of what we have been, but what we yearn to be.

Jose Ortega y Gasset Carl Sagan

The Code

The code to make this happen works in two steps:

  1. The original plugin defines hooks—both action hooks and filter hooks—allowing outside code to modify or add to its own code at key points. The plugin uses apply_filters() and do_action() to accomplish this.
  2. The extension defines hooked functions—both actions and filters—that modify the original plugin in the needed ways.

Key Code in the Original Plugin

We’re not going to cover how we make our API call to the quote database, but that’s quite interesting and is a textbook use of wp_remote_get(), so again, please feel free to download the ZIP file containing both plugins.

The important code for us is what we do once we’ve gotten our API data. It comes in as an array with two elements: text, the quote text; and author, the person who said the quote. Our code looks as follows:

add_shortcode( 'extensible_plugin_demo', 'wpshout_output_extensible_plugin' );
function wpshout_output_extensible_plugin() {
	ob_start();

	// Action hook before any output!
	do_action( 'wpshout_before_get_qod_text' );

	$qod_data = wpshout_get_qod_data();
	if( is_array( $qod_data ) ) {
		echo '<blockquote><small>';

		$qod_text = $qod_data[ 'text' ];
		$qod_author = $qod_data[ 'author' ];

		// Filter content!
		$qod_text = apply_filters( 'wpshout_qod_text', $qod_text );
		$qod_author = apply_filters( 'wpshout_qod_author', $qod_author );
		
		echo $qod_text;
		echo '<br><br>';
		echo $qod_author;
		echo '<small></blockquote>';
	}

	// Action hook after rest of output!
	do_action( 'wpshout_after_get_qod_text' );

	return ob_get_clean();
}

The pieces to emphasize here (besides how handy output buffering comes in for shortcodes) are the following four lines:

  1. do_action( 'wpshout_before_get_qod_text' ); This will run any functions that have been hooked (using add_action()) to the action hook that we’re naming wpshout_before_get_qod_text. This all happens before the rest of the plugin’s code gets printed out. This action hook is what the extension is using to print “Carl Sagan Quote of the Day for [date]:” above the quote itself.
  2. $qod_text = apply_filters( 'wpshout_qod_text', $qod_text ); This will run any functions that have been hooked (using add_filter()) to the filter hook that we’re naming wpshout_qod_text. These are filter functions, meaning that they modify the output they’re given—$qod_text, which is the text content of the quote—and then give it back for further processing. This filter hook is what the extension uses to convert particular words from the quote into Carl Saganspeak.
  3. $qod_author = apply_filters( 'wpshout_qod_author', $qod_author ); This will run any functions that have been hooked (using add_filter()) to the filter hook that we’re naming wpshout_qod_author. This filter hook is what the extension uses to cross out the original quote author and place “Carl Sagan” after it.
  4. do_action( 'wpshout_after_get_qod_text' ); This will run any functions that have been hooked (using add_action()) to the action hook that we’re naming wpshout_after_get_qod_text. This all happens after the rest of the plugin’s code gets printed out. This action hook is what the extension uses to display the “Mind Blown” graphic below the quote.

To sum up: the plugin could just output its own information, but it goes out of its way—using apply_filters() and do_action() to create hooks at key places in the code. Its action hooks let other programmers add in or modify whatever they want, and its filter hooks let other programmers modify specific pieces of key content.

Key Code in the Extension

The extension is made up of nothing but actions and filters—modifications to the original plugin that take advantage of the plugin making space for those modifications. Here’s the code for the extension:

add_filter( 'wpshout_qod_text', 'wpshout_filter_qod_text' );
// Filter: Replace specific words in quote text with Carl Saganspeak
function wpshout_filter_qod_text( $text ) {
	$text = str_replace( 
		array( ' I ', ' me ', ' the ', ' is ', ' am ', ' are ' ),
		array( ' I, a descendent of savannah-dwelling hominids that somehow evolved an astounding capacity for self-reflection, ', ' the cooled-off stellar matter I call "me" ', ' the trillions of atoms that make up the ', ' seems, without the mind-boggling perspective of quantum physics, to be ', ' cannot, due to the Heisenberg Uncertainty Principle, be determined not to be ', ' appear to our best scientific instruments to be ' ),
		$text
	);
	return $text;
}

// Filter: Strikethrough old author and add "Carl Sagan" after
add_filter( 'wpshout_qod_author', 'wpshout_filter_qod_author' );
function wpshout_filter_qod_author( $text ) {
	$text = '<strike>' . $text . '</strike> Carl Sagan';
	return $text;
}

// Action: Add title before quote
add_action( 'wpshout_before_get_qod_text', 'wpshout_set_up_quote' );
function wpshout_set_up_quote() {
	echo '<h4>Carl Sagan Quote of the Day for ' . date( 'F j, Y') . ':</h4>';
}

// Action: add "mind blown" image
add_action( 'wpshout_after_get_qod_text', 'wpshout_add_carl_boom' );
function wpshout_add_carl_boom() {

	echo '<div><img class="aligncenter" src="' . plugin_dir_url( __FILE__ ) . 'carl_sagan_mind_blown.gif"></div>';
}

As you can see, these are quite standard uses of add_action() and add_filter()—and both functions are hooking into the hooks we named (for example, wpshout_qod_text), just like they would any other hook.

The end result is that our plugin is extensible: a second developer can look at the daily quote plugin, say “I want to Carl Sagan-ize that,” and do it while still using the original plugin as a base—quite a bit like how parent and child themes work on the presentation side.

This trick, times about 1000, is what makes a giant plugin like WooCommerce able to have a rich extensions ecosystem—and integrate with MailChimp, Salesforce, Authorize.net, and your tax preparation software, all while hitting its core job of “listing products online” out of the park.

Now You Get Extensibility!

This article has given you an example of an extensible plugin. Ignoring what the market actually needs, our “Quote of the Day” plugin is perfectly set up from a technical standpoint for all kinds of innovations by third-party developers—all of whom have access to the plugin’s environment through the judicious use of WordPress hooks.

If you want take your next steps to learn WordPress development, check out our course Up and Running.

Happy extending, and don’t forget those hooks in your plugins!


3 Responses

Comments

  • I have been looking all over the net for info on hooks and your site has nailed it. My searching of the last 3 months is finally over and now I can get on with Object orientated PHP and really do something (with the help of jQuery) with WordPress.

    Thanks again, even my wife is relieved that I have stopped ranting about useless info when I do Google searches on hooks.
    I am in your debt, now maybe even my hair may grow back!

  • Riccardo says:

    Great article. It is always good reading your posts because they are easy to read and understand. Thank you.

    Riccardo

Pingbacks