Advertisement

Writing Extensible Plugins With Actions and Filters

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

One of the many things you will find when investigating a good plugin developer's source code are custom hooks and filters that the developer has placed throughout the plugin. The presence of these action hooks and filters make the plugin "extensible", meaning that other plugins and themes can manipulate or add onto the behavior of the plugin.

Writing extensible plugins is something I'd like to strongly encourage you to do. There are a lot of great reasons why you should do it, but very few (if any) reasons why you should avoid this practice.

We are going to look at several elements of extensible plugins:

  • An explanation of what extensible plugins are
  • Reasons why you should write extensible plugins
  • The basic tools you need
  • How you need to write your plugins to make them extensible
  • Simple examples of hooks and filters to illustrate how you might use them

One important note: this tutorial will be using purely procedural based programming techniques. Everything I talk about here still applies when using Object Oriented Programming (OOP), but it is simpler to first learn these techniques in a procedural setting.


What Is an Extensible Plugin?

An extensible plugin is one that can be modified and extended beyond its original purpose by another plugin or theme. When a plugin is extensible, other plugins (or themes) can alter the behavior or output of the plugin. For example, ecommerce plugins allow add-on payment gateways that permit purchases to be processed via additional payment systems. These payment gateways are separate plugins that simply connect to the core plugin and extend its functionality.

Any and all plugins can be extensible, though only a small minority of published plugins are. In order to be extensible, or modular (another term for the same thing), a plugin's developer must make a conscious decision to make it that way by implementing the necessary elements that make it possible for other plugins and themes to tie into the core plugin's behavior.


Why Write Extensible Plugins?

There are a lot of good reasons to write extensible plugins, but one of the main reasons is that there simply isn't a good reason not to write your plugins this way, especially not when your plugins are released to the WordPress community, either as free or paid plugins.

When you write extensible plugins, you make it possible for other developers to extend your plugin and make it even better, but without ever changing the core source code. You also make it significantly easier for developers and users to adapt the plugin to suit their needs better. Let me give you an example: in my Easy Digital Downloads plugin there is a discount code system. Discount codes are restricted to a single use per user, so if a user tries to apply the same discount on two different purchases, they will receive an error. One of my real-world users found that she did not want to limit discounts to a single use per user, so I gave her a simple function that she dropped into a new custom plugin and the restriction was removed, without touching any core plugin code.

Extensible code also makes other developers extremely happy when they find it because their job of adapting your code just became much, much easier.


The Basic Tools / Functions

There are several key tools you need in order to write extensible plugins. If you have written any plugins or themes before reading this, you are probably at least somewhat familiar with these functions, even if it is only because you have seen them used.

Before I show you the functions you will use, let's talk about two main concepts first: hooks and filters.

An action hook is a place in your plugin that can be "hooked" into by functions (both in your plugin and other plugins) in order to have their code executed at that point. When an action hook runs, all functions that are connected, or "hooked", to it will run as well.

A filter hook is also a place in your plugin for other functions to tie into, but they work slightly differently than actions. Filters allow for data to be manipulated or modified before it is used.

The key difference between actions and filters is that actions are usually used for executing functions and filters are usually used for manipulating data.

Unless you're already highly familiar with actions and filters, I'd strongly encourage you to go read the Codex entry on them.

For actions there are four main functions:

  • do_action() - this defines a hookable location for actions
  • add_action() - this attaches a function to a hook created with do_action()
  • has_action() - this checks to see if an action has been registered with do_action()
  • remove_action() - this removes an action that was set with add_action()

Of these four, you will use do_action() and add_action() the most.

For filters there are also four main functions:

  • apply_filters() - this creates a hookable location for custom filters to tie into
  • add_filter() - this attaches a custom filter to a hook created with apply_filters()
  • has_filter() - this checks to see if a filter has been registered with apply_filters()
  • remove_filter() - this removes a filter previously connected to apply_filters()

As with actions, apply_filters() and add_filter() are the two you will use the most.

If you're confused at this point, do not worry, we are going to look at how to actually use these in the next section.


Implementation in Your Own Plugins

In order to make your plugin truly extensible, you need to use the key functions mentioned above throughout the entire plugin. At first you may find it difficult, cumbersome, annoying, or many other applicable adjectives, to constantly place these additional functions throughout your code, especially when you do not see an immediate benefit or use for them.

What you will find, however, is that once you are in the habit of writing your plugins with all of these functions in mind, it will become second nature to include them.

There are a few main scenarios where you will use filters in your plugins:

  • When arrays are setup. Filters will be added here so that other plugins can modify the data before it is used.
  • When data objects are setup. Just like with arrays, you will use a filter on objects so that other developers can alter the object before it is used.
  • When data strings are setup. With a filter available on a string, other developers can change the entire string, alter parts of it, or add onto it.

Of the scenarios above, it is most common to use filters when data is returned or just before it is used. For example, if you have a plugin that performs a posts query, it's best to pass the array of query arguments through a filter before they are passed to get_posts() or WP_Query so that others can manipulate the query before it is made.

When it comes to actions, there are also several main instances where you will place them:

  • Before a task is executed.
  • After a task is executed.
  • Within your markup to allow additional markup to be inserted.

Let's consider a few examples now.

1. Displaying HTML With a Shortcode

Shortcodes that output HTML are extremely common (actually they are probably the most common of all shortcodes), and one of the ways that we can make our plugin's shortcodes more friendly to other developers is by providing a way for them to alter the contents of the shortcode, but without requiring that it be deregistered and registered again.

All shortcodes return rather than echoing their contents, which means that the data outputted to the screen is going to be in the form of a string before it is returned. Because the entire HTML output is in the form of a string, you can pass the string through a filter before returning it. Doing so will make it possible for other developers to alter the HTML of your shortcode.

A developer may want to add additional markup before and after the default HTML: with the filter in place, they can do that.

Your shortcode may look something like this:

function wptp_sample_shortcode( atts, $content = null ) {

	$html = '<div class="wptp_shortcode">';
		$html .= '<p>Contents of the sample shortcode</p>';
	$html .= '</div>';

	return $html;

}

We can improve this by adding a filter to the return, like so:

function wptp_sample_shortcode( atts, $content = null ) {

	$html = '<div class="wptp_shortcode">';
		$html .= '<p>Contents of the sample shortcode</p>';
	$html .= '</div>';

	return apply_filters( 'wptp_shortcode_html', $html );

}

The HTML in our shortcode can now be modified like this:

function wptp_modify_html( $html ) {
	return '<div class="extra_div">' . $html . '</div>';
}
add_filter( 'wptp_shortcode_html', 'wptp_modify_html' );

This will result in the original HTML created in the shortcode being wrapped with another div tag.

2. Querying Posts

Performing custom queries in plugins is a common practice. Let's assume for a moment that you have written a plugin that registers a custom post type called "books", and in your plugin is a function to show created books. Your function to query the books might look something like this:

function wptp_show_books() {

	$query_args = array(
		'post_type' => 'books',
		'posts_per_page' => 5
	);

	$books = new WP_Query( $query_args );
	if( $books->have_posts() ) :
		while( $books->have_posts() ) : $books->the_post()
			// show info about each book here
		endwhile;
	endif;
	wp_reset_postdata();

}

But what if a user wanted to modify the kinds of books that were returned, perhaps selecting only books from a specific category? You can make this much easier for them by doing this:

function wptp_show_books() {

	$query_args = array(
		'post_type' => 'books',
		'posts_per_page' => 5,
		'author' => 3
	);

	$books = new WP_Query( apply_filters( 'wptp_books_query', $query_args ) ) ;
	if( $books->have_posts() ) :
		while( $books->have_posts() ) : $books->the_post()
			// show info about each book here
		endwhile;
	endif;
	wp_reset_postdata();

}

The only change I made was to add a filter around the $query_args, which means other developers (or users) can modify the query arguments before they are actually passed to WP_Query. For example, we could set the query to only show books from author 3 like this:

function wptp_alter_books_query( $args ) {
	$args['author'] = 3;
	return $args;
}
add_filter( 'wptp_books_query', 'wptp_alter_books_query' );

3. Extending Markup

Let's extend on example number 2 now and make it even better. We already added a filter that allows users to modify the query, now let's add a few hooks to let us alter the HTML that is created.

First we'll modify our original HTML a bit:

function wptp_show_books() {

	$query_args = array(
		'post_type' => 'books',
		'posts_per_page' => 5,
		'author' => 3
	);

	$books = new WP_Query( apply_filters( 'wptp_books_query', $query_args ) ;
	if( $books->have_posts() ) :

		echo '<div class="wptp_books">';

			while( $books->have_posts() ) : $books->the_post()

				echo '<div class="wptp_book">';

					echo '<h3 class="wptp_book_title">' . get_the_title() . '</h3>';

				echo '</div>';

			endwhile;

		echo '</div>';

	endif;
	wp_reset_postdata();

}

We would now like to make it possible for developers to add in extra markup at various points, such as the following:

  • Before any HTML is outputted
  • After the ending HTML
  • Before the title of each book
  • After the title of each book

You can imagine a scenario where a user would want to add a thumbnail before or after the book title. To make this possible, we use do_action() to create hookable locations, like this:

function wptp_show_books() {

	$query_args = array(
		'post_type' => 'books',
		'posts_per_page' => 5,
		'author' => 3
	);

	$books = new WP_Query( apply_filters( 'wptp_books_query', $query_args ) );
	if( $books->have_posts() ) :

		do_action( 'wptp_books_before' );
		echo '<div class="wptp_books">';

			while( $books->have_posts() ) : $books->the_post()

				echo '<div class="wptp_book">';

					do_action( 'wptp_before_book_title', get_the_ID() );

					echo '<h3 class="wptp_book_title">' . get_the_title() . '</h3>';

					do_action( 'wptp_after_book_title', get_the_ID() );

				echo '</div>';

			endwhile;

		echo '</div>';
		do_action( 'wptp_books_after' );

	endif;
	wp_reset_postdata();

}

Note that the two inner hooks (surrounding the title) have a second parameter of get_the_ID(). This variable, which will be the ID of the book, will be available as a parameter to any hooked function. To add in a book thumbnail, for example, we can do this:

function wptp_show_book_image( $book_id ) {
	echo get_the_post_thumbnail( $book_id, 'thumbnail' );
}
add_action( 'wptp_before_book_title', 'wptp_show_book_image' );

Real World Examples

I would like to now show you some real world examples of plugins that are extensible, including samples of some of their extensible functions.

1. Soliloquy

Soliloquy is a powerful WordPress responsive image slider plugin that makes creating and maintaining responsive, efficient, secure and SEO friendly image sliders a breeze.

Nearly everything in this plugin is extensible. Here's just one example:

$labels = apply_filters( 'tgmsp_post_type_labels', array(
	'name' 					=> __( 'Soliloquy', 'soliloquy' ),
	'singular_name' 		=> __( 'Soliloquy', 'soliloquy' ),
	'add_new' 				=> __( 'Add New', 'soliloquy' ),
	'add_new_item' 			=> __( 'Add New Soliloquy Slider', 'soliloquy' ),
	'edit_item' 			=> __( 'Edit Soliloquy Slider', 'soliloquy' ),
	'new_item' 				=> __( 'New Soliloquy Slider', 'soliloquy' ),
	'view_item' 			=> __( 'View Soliloquy Slider', 'soliloquy' ),
	'search_items' 			=> __( 'Search Soliloquy Sliders', 'soliloquy' ),
	'not_found' 			=> __( 'No Soliloquy Sliders found', 'soliloquy' ),
	'not_found_in_trash' 	=> __( 'No Soliloquy Sliders found in trash', 'soliloquy' ),
	'parent_item_colon' 	=> '',
	'menu_name' 			=> __( 'Soliloquy', 'soliloquy' )
) );

$args = apply_filters( 'tgmsp_post_type_args', array(
	'labels' 				=> $labels,
	'public' 				=> true,
	'exclude_from_search' 	=> true,
	'show_ui' 				=> true,
	'show_in_admin_bar'		=> false,
	'rewrite'				=> false,
	'query_var'				=> false,
	'menu_position' 		=> 100,
	'menu_icon' 			=> plugins_url( 'css/images/menu-icon.png', dirname( __FILE__ ) ),
	'supports' 				=> array( 'title' )
) );

This is how Thomas Griffin (the plugin's developer) sets up the arguments for both the custom post type labels and attributes. The presence of his two filters, tgmsp_post_type_labels and tgmsp_post_type_args, make it very simple for other developers to rename the slider post type or change what the post type supports.

2. bbPress

By far one of my personal favorite plugins of all time, bbPress is a fully featured forum plugin for WordPress. The entire plugin is a perfect example of how to make your plugins extensible, as it literally has actions and filters everywhere. It has one particular filter that is applied when retrieving the content of a forum:

function bbp_get_forum_content( $forum_id = 0 ) {
	$forum_id = bbp_get_forum_id( $forum_id );

	// Check if password is required
	if ( post_password_required( $forum_id ) )
		return get_the_password_form();

	$content = get_post_field( 'post_content', $forum_id );

	return apply_filters( 'bbp_get_forum_content', $content, $forum_id );
}

Before the content of the forum is returned, it is passed through a filter called bbp_get_forum_content that makes it possible for a developer to modify the content before it is ever displayed.

3. Easy Digital Downloads

Easy Digital Downloads, or EDD, is one of my plugins that was built to make it exceptionally easy to sell digital products through WordPress. As with most ecommerce plugins, EDD has a checkout process that the buyer goes through so they can enter their personal and payment details. After all of that info is collected, it all goes to a payment gateway (a system for processing the payment), but before it goes to the gateway, a filter is applied that allows the data to be manipulated before it is used by the payment system:

$purchase_data = apply_filters(
	'edd_purchase_data_before_gateway',
	$purchase_data,
	$valid_data
);

The presence of this filter makes it possible to adjust purchase amounts (perhaps for special discounts), add taxation, perhaps add or remove products from the purchase, and much, much more.


Conclusion

Extensible plugins benefit everyone: the original developer, other developers, and the users themselves.

There are so many reasons why you should write your plugins with extendable code in mind, so why don't you?

The tools presented here are everything that you need to get started. Have questions? Ask away!

Advertisement