WordPress Hooks: Actions and Filters

One of the greatest things about WordPress, is undoubtedly its customizability. Not only it provides various APIs to make our lives easier by abstracting and hiding complex code and processes, but also allows us to intervene and modify its default behavior. It also gives us the ability to provide the same level of customizability in our own code. If you’re regularly reading tutorials about WordPress or you’re tinkering with PHP, chances are you’ve come across and interacted with hooks.

What are hooks?

As a method of providing customizability, WordPress provides hooks. Put simply, hooks can be though of as events or points of interest in code, that we may want to intervene. For example, when a post is deleted we might want to do something. There’s a hook for that. When WordPress determines how many posts per pages it should show, we might want to change it depending on the current view. There’s a hook for that too.

There are two kinds of hooks however, that serve two different purposes: Actions and Filters.

What are actions?

As the name suggests, actions are executed (or “fired”) usually when something interesting happens and you might want to “take an action”. Actions may also fire on specific parts of code, for example on a theme’s template files, so that you hook your own functions and display something that the theme’s developer doesn’t display. When actions are fired, they may provide any number of parameters (or none at all) that we can use in our hooked functions. While the parameters passed are useful as they provide context, they are usually not meant to be manipulated in a way that will affect any behavior outside our own hooked function, although there are notable examples such as the pre_get_posts action that does exactly that. Similarly, nothing needs to be returned from our hooked function, as there’s no purpose, and the value isn’t used anywhere.

What are filters?

Filters are very similar in nature with actions, with their main difference being that their purpose is to affect something by returning a value. Filters are usually used before an action takes place, so that we have a chance of modifying its parameters, as well as after the action took place, so that we have a change to modify its output. Filters are also commonly used to short-circuit code, for example disabling a feature by returning a boolean false value, or the exact opposite, return true to enable a feature.

Let’s take a look at how actions and filters are used.

Using hooks

Please note this; it is important: No matter the type of hook, you need to hook your functions before the desired hook fires. Always keep that in mind, otherwise you’ll probably end up trying to debug irrelevant pieces of code.

Having said that, there are three things in total that are required in order for a hooked function to get called:

  1. A function needs to exist.
  2. The function needs to be hooked on a hook.
  3. The hook in question needs to eventually fire.

It seems that #3 is one of the key requirements, as otherwise there’s nothing to hook onto, however I wanted to reinforce the importance of the order of execution. Now that we know the requirements, let’s jump on specifics.

Using actions

All an action needs to get fired is this:

do_action( 'an_action_name' );

Pretty simple right? When this code executes, WordPress will search for any functions that have been hooked, and call them one by one. But wait; there’s nothing to call! Let’s fix that!

function a_hooked_function() {
	echo 'Hey!';
}
add_action( 'an_action_name', 'a_hooked_function' );

/* ... */

do_action( 'an_action_name' );

This is it really. It follows the 3 required steps I mentioned earlier; a function is defined (if required), line 4 hooks the a_hooked_function() onto the ‘an_action_name‘ hook, and at some point later the ‘an_action_name‘ fires.

It doesn’t matter whether this code will go in a theme, a child theme or a plugin. In my case, I’ve added it in a plugin, and the following output is of my local installation of our newest free theme, Public Opinion Lite:

Yeah, not very beautiful, and not very useful either!

Our “Hey!” was the first thing that was printed on page, because as soon as the file we’re working on was read, the function was hooked and the hook immediately fired. WordPress fires quite a lot of hooks on every page request, and hooking on them allows our hooked functions to execute at specific points of the request’s lifetime. With the Actions Reference in hand, let’s see what would be the appropriate way to print a message at the end of our page (a.k.a. its footer). The reference mentions that the ‘init‘ action is typically used by plugins to initialize. Also, ‘wp_footer‘ is the last action hook that themes must call (via the wp_footer() function). Our code would transform like so:

function a_hooked_function() {
	echo 'Hey!';
}
add_action( 'wp_footer', 'a_hooked_function' );

It’s very similar to our previous code. In this case though, we don’t trigger an action via a do_action() call, but we use the action WordPress already provides. Can you see where this is going? Find a suitable action, and you can hook anywhere, be it WordPress itself or a 3rd party plugin.

But I mentioned the ‘init‘ action and I didn’t use it! Let’s use it now:

function hooks_tutorial_init() {
	add_action( 'wp_footer', 'a_hooked_function' );
}
add_action( 'init', 'hooks_tutorial_init' );

function a_hooked_function() {
	echo 'Hey!';
}

Did I just hooked a function, in order to hook another function?

Well, it’s a very good practice that makes our theme/plugin extendable. When we add code outside an action, it will run in the order that the PHP files are loaded. If your plugin A needs to do something before someone else’s plugin B, but the A’s files are loaded after B’s, well, then you have a problem. If however B does its thing inside an action hook, say ‘init‘ for example, then you can have your A run before B, by assigning it a higher priority.

Priorities

As there may be any number of functions hooked to a specific action, we sometimes need a way to control and change the order they get executed in order to achieve a specific result. A hooked function’s priority is a number that determines the order it will be executed. We provide this number as the first parameter of the add_action() call, or we can omit it and it will default to 10. The lower the number, the higher the priority, the earlier the function will get called. When two hooked functions have the same priority, then the order of the add_action() calls comes into play, with the function hooked first being executed before the other.

function hooks_tutorial_init() {
	add_action( 'wp_footer', 'another_hooked_function', 10 );
	add_action( 'wp_footer', 'a_hooked_function', 10 );
}
add_action( 'init', 'hooks_tutorial_init' );

function a_hooked_function() {
	echo 'Hey!';
}

function another_hooked_function() {
	echo 'How are you this fine evening?';
}

Building upon our previous example, we’re hooking another function that also simply displays a string. Note that I’ve hooked the another_hooked_function() right before our previous a_hooked_function(). Refreshing our page, we’ll see that the message “How are you this fine evening? Hey!” is printed, which is the opposite of what we really want. One way to fix this is to swap lines 2 and 3, but for the purposes of this tutorial, we’ll simply change the another_hooked_function()‘s priority to 11.

function hooks_tutorial_init() {
	add_action( 'wp_footer', 'another_hooked_function', 11 );
	add_action( 'wp_footer', 'a_hooked_function', 10 );
}
add_action( 'init', 'hooks_tutorial_init' );

function a_hooked_function() {
	echo 'Hey!';
}

function another_hooked_function() {
	echo 'How are you this fine evening?';
}

What do you know! It works! By simply changing the priority to 11, we force our new function to get called later, giving us the desired result.

Priorities can be any valid integer (including negative numbers) but no one really does that. They are usually greater than 0.

Parameters

An action can send, and a hooked function can receive, any number of parameters. These are usually used to provide some kind of context. In order to include parameters in a do_action() call, all we need is to include them after the action’s name. Consider the following simple shortcode that prints the current date:

add_shortcode( 'the-date', 'my_plugin_shortcode_print_date' );
function my_plugin_shortcode_print_date( $params, $content = null, $shortcode ) {
	$params = shortcode_atts( array(
		'format'    => get_option( 'date_format' ),
		'timestamp' => false,
	), $params, $shortcode );

	if ( false === $params['timestamp'] ) {
		$timestamp = current_time( 'timestamp' );
	} else {
		$timestamp = $params['timestamp'];
	}

	ob_start();

	do_action( 'the_date_shortcode_before', $timestamp, $params['format'], $shortcode );

	echo date_i18n( $params['format'], $timestamp );

	do_action( 'the_date_shortcode_after', $timestamp, $params['format'], $shortcode );

	return ob_get_clean();
}

Lines 16 and 20 trigger actions that allow us to do something before and after our shortcode echoes its output. Both actions pass a timestamp as the first, the requested date format string as the second, and the shortcode’s name as the third parameter. If we want to receive these parameters, we need to do two things: a) declare the number of parameters our function will be accepting, by passing a number right after the priority in our add_action() call, and b) set said function to accept three parameters, e.g.:

add_action( 'an_action_name, 'a_hooked_function', 10, 3 );

So in for our specific example, if we want to do something before and after the shortcode’s output (for example, if it’s a Sunday, make the date stand out), we’d do:

function hooks_tutorial_init() {
	add_action( 'the_date_shortcode_before', 'hooks_tutorial_before', 10, 3 );
	add_action( 'the_date_shortcode_after', 'hooks_tutorial_after', 10, 3 );
}
add_action( 'init', 'hooks_tutorial_init' );

function hooks_tutorial_before( $timestamp, $format, $shortcode ) {
	if ( 7 == date( 'N', $timestamp ) ) {
		echo '<strong>';
	}
}

function hooks_tutorial_after( $timestamp, $format, $shortcode ) {
	if ( 7 == date( 'N', $timestamp ) ) {
		echo '</strong>';
	}
}

Inside our function we can use $timestamp, $format and $shortcode to do whatever it is we need to do. In this specific example however, we only have use for the $timestamp parameter. Isn’t it an overkill and perhaps a source of confusion including all 3 parameters unnecessarily? It is, and we can get only the number of leftmost parameters that we need. If we only need the first two parameters, our code would become:

function hooks_tutorial_init() {
	add_action( 'the_date_shortcode_before', 'hooks_tutorial_before', 10, 2 );
	add_action( 'the_date_shortcode_after', 'hooks_tutorial_after', 10, 2 );
}
add_action( 'init', 'hooks_tutorial_init' );

function hooks_tutorial_before( $timestamp, $format ) {
	/* ... */
}

function hooks_tutorial_after( $timestamp, $format ) {
	/* ... */
}

Similarly for just the first parameter. However, the add_action() number of parameter defaults to 1 if omitted, so we can do just that; omit it.

function hooks_tutorial_init() {
	add_action( 'the_date_shortcode_before', 'hooks_tutorial_before', 10 );
	add_action( 'the_date_shortcode_after', 'hooks_tutorial_after', 10 );
}
add_action( 'init', 'hooks_tutorial_init' );

function hooks_tutorial_before( $timestamp ) {
	/* ... */
}

function hooks_tutorial_after( $timestamp ) {
	/* ... */
}

I personally like to include the number 1 whenever I use one parameter, so that I know my hooked function accepts exactly one parameter just by looking on the add_action() call.

Finally, just because the action sends 3 parameters, doesn’t mean we have to use them if we don’t need them.

function hooks_tutorial_before() {
	/* ... */
}

add_action( 'the_date_shortcode_before', 'hooks_tutorial_before', 10 );

do_action( 'the_date_shortcode_before', $timestamp, $params['format'], $shortcode );

Using filters

As I mentioned earlier, filters are similar to actions but filters are used to affect a value. In fact, the only difference between a filter and an action is that the former returns a value whereas the latter does not. Similarly to actions, filters are managed via the add_filter() and apply_filters() functions. If you dive into the source, you’ll find that actions are (at least as far as their registration is concerned) identical to filters. In fact, all add_action() does is to call add_filter(), but maybe it’s better to ignore this fact, so that we can mentally distinguish between the two.

Applying a filter, that is to call all hooked functions, is done via the apply_filters() function which is used similarly to do_action(), but two parameters are required in this case. The first is the filter’s name, and the second is the value that we want hooks to be able to modify. The result is returned, so we should put it to use somehow (assign it in a variable, echo it, etc). Of course, we can include as many other parameters as we want, just like with actions.

Using filters in our shortcode example, the code would change like this:

add_shortcode( 'the-date', 'my_plugin_shortcode_print_date' );
function my_plugin_shortcode_print_date( $params, $content = null, $shortcode ) {
	$params = shortcode_atts( array(
		'format'    => get_option( 'date_format' ),
		'timestamp' => false,
	), $params, $shortcode );

	if ( false === $params['timestamp'] ) {
		$timestamp = current_time( 'timestamp' );
	} else {
		$timestamp = $params['timestamp'];
	}

	ob_start();

	do_action( 'the_date_shortcode_before', $timestamp, $params['format'], $shortcode );

	$date_string = date_i18n( $params['format'], $timestamp );
	$date_string = apply_filters( 'the_date_string', $date_string, $timestamp, $params['format'], $shortcode );
	echo $date_string;

	do_action( 'the_date_shortcode_after', $timestamp, $params['format'], $shortcode );

	return ob_get_clean();
}

The output of date_i18n() is passed through the ‘the_date_string‘ filter, and if there are no hooks, the exact same value will be returned. This may also be the case even if there are hooked functions, since they may elect not to modify the value depending on the use-case.

Re-using the example of adding a <strong> tag if the date is a Sunday, would change our code like this:

function hooks_tutorial_init() {
	add_filter( 'the_date_string', 'hooks_tutorial_date_filter', 10, 2 );
}
add_action( 'init', 'hooks_tutorial_init' );

function hooks_tutorial_date_filter( $value, $timestamp ) {
	if ( 7 == date( 'N', $timestamp ) ) {
		$value = '<strong>' . $value . '</strong>';
	}

	return $value;
}

Line 2 hooks our function on the ‘the_date_string‘ filter, requesting only 2 parameters. Then, inside the hooks_tutorial_date_filter() function we concatenate the <strong> tags only if our check passes. We return $value unconditionally however, since we need to return a value. If we omitted the return statement then the shortcode’s $date_string would become null, and echoing it would produce nothing visible.

That’s all on what filters do, really. As anything, they are a tool and their use is only limited by your imagination. You could enable or disable your plugin’s options depending on the value of a filtered variable, or you could give a chance on other plugins/developers to add their own information by filtering your plugin’s output.

Conclusion

The actions and filters (cumulatively known as hooks) system, part of the WordPress plugin API, is one of the greatest features of WordPress itself. By giving it a little though and using it ourselves, we can build themes and plugins that are extendable and developer friendly, which is the basis of every great theme and plugin out there.

Can you imagine WooCommerce without extensions? How about WordPress without plugins? It’s all possible due to the presence of actions and filters at key places, so do everyone a favorĀ (including yourself) and incorporate hooks on your code.

Let us know in the comments if you have any questions, typos, or hooks’ love/hate stories.

Leave a Reply

Your email address will not be published. Required fields are marked *