Ever been told the solution to your problem is that you need to “hook onto” something to do your thing or used add_filter without really understanding why it messes stuff up? Hooks make up the foundation of WordPress and provides themes and plugins a way to interact or modify code. In this post we’ll break down what hooks are, how they work and how we can use them.

What are hooks?

As WordPress runs through its code (all the way from the start of loading classes, loading plugins, loading the theme, making the query for the current request and finally rendering the HTML), it runs through a bunch of “checkpoints”; hooks. When a hook is encountered, WordPress will stop what it’s doing – and check if there are any functions (in theme, plugin or WordPress itself) that is registered to run at this hook. If there are, WordPress will run all of those functions, and then continue where it left off.

This makes it possible for plugins, themes and WordPress itself to “hook onto” certain points to add e.g. stylesheets in the theme’s <head> tag, modify how WordPress queries posts, or simply change the post content if it so desired.

There are two types of hooks; actions and filters. They work pretty similar to each other, but with one exception which we will look closer at below.

Actions and filters

To hook onto an action or filter, you need to define which hook (by its name) you wish to use, and define a callback – which is the name of the function you wish to run. In most cases the hooks provide parameters or variables that are relevant to the hook.

Actions are simply “checkpoints” in the execution of WordPress, where you can perform a task or echo something. In some rare cases you can modify an object (provided in parameters), so that all following execution work with that changed object.

Filters are for changing a variable or output. All filter hooks will provide the variable you can change as first parameter, and allow you to modify it or return something different.

WordPress runs many hooks, but you can create your own in your plugin or theme. This allows developers to extend and modify your code; for example modifying a plugin via a theme, or modifying the parent theme via the child theme.

A lot of WordPress’ functions, typically used for printing out posts, are also filters or actions. For example the function the_title() prints out the post’s title, but it is also a filter.

Let’s look into how we hook onto these in practice.

Hooking onto actions

In order to hook onto an action, you use the method add_action(). As minimum it requires two parameters; the hook name and the callback; the name of the function you wish to run. Optionally you can provide priority as third parameter, and defining the number of arguments that will be passed to your callback function. We will look into the two optional parameters later, but for now, here is an example of add_action:

add_action('init', 'my_function_name');
function my_function_name() {
	// Do your stuff
}

The first parameter to add_action is the name of the action; in this case init, which is a pretty common WordPress hook that happens during initializing. The second parameter is your callback, in this case the name of the function we wish to run. Then all you need to do is define a function with that name and add your code inside of it.

You can also use anonymous functions which is where you define the function inside add_action instead of providing a function name, like so;

add_action('init', function() {
	// Do your stuff
});

Many developers prefer this method as this prevents the risk of redefining a PHP function with the same name.

If you need to run a function defined inside a PHP class, you need to provide an array as the callback parameter; where the first element is the class object and the second element is the function name:

class MyClass {
	public function __construct() {
		add_action('init', [$this, 'myFunctionName']);
	}

	public function myFunctionName() {
		// Do your stuff
	}
}

Hooking onto filters

In order to hook onto a filter, you use the function add_filter(). Similarly to actions above, first parameters is the filter name, and the second is your callback. Optionally you can provide priority as third parameter and the number of arguments as fourth. Here is an example of add_filter:

add_filter('the_title', 'my_function_name');
function my_function_name($title) {
	// Do your stuff
	return $title;
}

In the above example we hook onto the filter the_title (which prints out the post title) and tells WordPress to run our function. All filters will always provide one argument; the variable to change. In my function I named it $title as I know it contains the post title. Inside the function I can modify it or completely override the variable. It’s important to remember that in filters you always need to return the variable. If you don’t return something in your callback function, the variable empties. If I skipped the return statement in the above example, no post titles would ever echo anywhere.

The variable argument and the rule of returning something in your callback function is practically the only difference between filters and actions. Everything else works the same. You can follow the examples for anonymous functions and classes shown for actions above for filters, as well as the optional parameters priority and number of arguments; which we will look at next.

Priority

The third (optional) parameter to add_action and add_filter is an integer that defines the priority of your callback function. This is useful in cases where there are multiple functions (not necessarily from you in your theme or plugin, remember that WordPress itself uses its own hooks), and you need to decide which one to run first.

If no priority as provided, it defaults to 10. The lower the priority, the earlier it is run, and the higher priority, the later it is run. You cannot provide a negative priority.

Imagine multiple callback functions registered to init. If no priority was provided for multiple callbacks, WordPress will run them at the order they were found (for example in functions.php or your plugin code).

add_action('init', 'my_function_name');
add_action('init', 'my_second_function_name');
add_action('init', 'my_third_function_name', 12);
add_action('init', 'my_fourth_function_name', 1);

This would be the order in which WordPress will run the above callbacks:

  1. my_fourth_function_name (priority 1)
  2. my_function_name (priority 10)
  3. my_second_function_name (priority 10, but appears later in code)
  4. my_third_function_name (priority 12)

Number of arguments

Usually hooks provide some additional data which are relevant and useful for the callback functions. For example the action save_post (runs whenever a post gets updated) provides two possible arguments; the post ID and the post object. These are useful for operations you commonly need to run at this hook (for example if you want to save a post meta, you need the post ID).

If no number of arguments was provided for an action or filter, it defaults to 1. This is why you don’t need to define 1 on add_filter to access the variable the filter is hooked onto. Actions, however, can pass no arguments even though the number of arguments is set to 1 as default.

Let’s look at an example of defining number of arguments and how we access them in our callback function:

add_action('save_post', 'my_function_name', 10, 2);
function my_function_name($post_id, $post) {
	// Do your stuff
}

In the above example, we tell WordPress to pass two arguments to our callback function. Remember that we need to define priority as third parameter so usually we define the default, which is 10. As for our callback function we can now define the same number arguments we asked for, in the example above was 2.

If we had in the above example set number of arguments to 1, only the first argument in our callback function would be populated. The second, $post, would be undefined.

What if we need data that were not passed in the hook?

It’s entirely up to the developer who created the hook to define what arguments can be passed. This means you cannot simply force what arguments you wish or need in your add_action or add_filter. Luckily in most cases we have alternatives to get variables we need. If you are hooking onto an action or filter where you know certain global variables should be defined, you can access these global variables in your callback function. Also, WordPress has a whole range of conditional tags you can use in your hooked functions (with the exception of the very early hooks which occurs before WordPress defines these conditional tags)

Let’s look at an example of accessing global variables. The filter the_title provides the post ID as optional argument, which we can access by defining it as acceptable argument;

add_filter('the_title', 'my_function_name', 10, 2);
function my_function_name($title, $post_id) {
	if ($post_id == 1) {
		return 'First post ever!';
	}
	return $title;
}

However, we can achieve the same result by;

add_filter('the_title', 'my_function_name');
function my_function_name($title) {
	global $post;
	if ($post->ID == 1) {
		return 'First post ever!';
	}
	return $title;
}

If you know a global variable is defined by the point the hook is run, you can define them as global in your callback function and access the data from there. For example defining global $wp_query in the above example would also give you access to the full wp_query object inside your function callback.

WordPress conditional tags are extremely useful. Unless you are hooked onto a hook that occurs very early in the execution of WordPress, they are available. There are conditional tags for checking which page (template) you are at, if you are inside a menu or a post loop, and much more. For example, when modifying the post query using the hook pre_get_posts it’s very useful to make sure your code only runs in certain cases. For example;

add_action('pre_get_posts', 'my_function_name');
function my_function_name($query) {
	if (!is_admin()) {
		// Do your stuff
	}
}

Because the hook pre_get_posts run both in admin and frontend, we can use a conditional tag to ensure our code only affects the frontend query.

Creating your own hooks

You can define your own actions and filters. If you are a theme or plugin developer, you are encouraged to do so, as to allow others to modify your code without changing the source code.

Register an action with do_action(), and register a filter with apply_filters().

The do_action() requires a minimum of 1 parameter; the hook name. Keep in mind that the hook name must be unique (don’t call it e.g. init as this is a core WordPress hook). Place the do_action() wherever you want the hook to appear. For example you can place the hook in your theme’s header.php, right after body as to define a hook where developers can output scripts or other content.

header.php
...
<body>
	<?php do_action('mytheme_after_body'); ?>

You can add as many parameters to do_action as you want after the hook name. Try to keep in mind what data developers would need in your hooks, that are not easily accessible otherwise.

The apply_filters() requires a minimum of 2 parameters; the hook name, and the variable which you are registering the filter on. For example, your theme can register a filter around some HTML classes to allow developers to modify or add to these:

<section class="<?php echo apply_filters('mytheme_section_classes', 'section default-class'); ?>">
	...
</section>

As with actions, you can add as many parameters to apply_filters as you want after the two mandatory parameters.

Conclusion and useful resources

With this I hope you have achieved a solid understanding of how WordPress runs most of its code, how you can modify code, and how you as a developer can allow other developers to make modifications to your code as well.