Web Development

The hooks system can seem daunting to the uninitiated, but they're one of the best things about WordPress. This guide will help you understand the fundamentals of hooks and how to use them, providing example usage and further advice.

Why should you use hooks?

In case you’re not already aware, it’s a really bad idea to change core files in WordPress (WP). Security risks aside, your changes will be overwritten every time you update WP! The same goes for plugins and themes.

Sure, you could keep track of every update and re-implement your changes. You could also punch through a beehive to get at the honey. It might seem convenient but you’re likely to get stung sooner or later.

Put simply, you should use hooks because they offset the risks of changing core functionality.

In this sense the importance of hooks can’t be overstated. They are key to WP extensibility and a cornerstone of its plugin and theme ecosystems. You’ll save yourself a lot of headaches by learning the basics and utilising them well.

What are they?

You can think of hooks as events that occur throughout the WP load sequence. When WP encounters a hook, it fires all functions attached to that hook before continuing.

You can thus leverage hooks to add functionality or change variables throughout WP. You can use existing hooks and add new ones. You can remove the hooks you don’t need. You can hit the same hook more than once, prioritising each function as you see fit. It’s a very flexible system, limited only by what hooks are available and what data they expose.

How do they work?

You add one more or more functions to a hook. Other functions may also be added by your theme, plugins or WP itself. When WP loads it will encounter the hook. It checks a list of all functions associated with the hook. WP runs all of these functions in priority order. Once done, it resumes loading everything after the hook.

What do they look like?

Adding a function to a hook looks like this:

add_action( $hook_name, $function_name, $priority, $arguments );

$hook_name is the name of the hook you want to target

$function_name is the name of the function you want to add to that hook

$priority is an integer indicating the $function_name‘s priority in $hook_name‘s queue. The lower the number, the higher the priority. Starting at 1, this number can be as high as you want (within system limits) but you’re unlikely to see it higher than 40. The default value is 10.

$arguments is an integer indicating the number of arguments you are using in the function. This must match the number of arguments passed to the function. The default value is 1.

Removing a function from a hook is almost the same:

remove_action( $hook_name, $function_name, $priority );

The $function_name and $priority must match their original values from the corresponding add_action function, or they won’t be removed!

Types of hook

There are two main types of hook: action hooks and filter hooks. They’re similar in many respects – add_action() and its counterpart add_filter() are both called in the same way. The difference is in the function you pass through:

// Run some code
add_action( 'where_to_do_the_thing', 'do_the_thing' );
function do_the_thing() {
   // Perform some action required to do the thing
}

// Change a variable
add_filter( 'the_thing', 'change_the_thing' );
function change_the_thing( $thing ) {
   // Filter $thing and change to whatever you need, then...
   
   return $thing;
}

Note that in the second example, the first argument passed to the function is the value being amended. Any additional arguments would be for utility only and are not passed back through the hook.

You may have noticed that I left out $priority and $arguments. This is because the default values are fine. If it helps, the above is essentially the same as:

// Run some code
add_action( 'where_to_do_the_thing', 'do_the_thing', 10, 1 );
// Change a variable
add_filter( 'the_thing', 'change_the_thing', 10, 1 );

These are contrived examples of course, but they illustrate the main difference between the two: filters are for changing variables, actions are for adding code.

Less contrived examples

Let’s say you want to have WP send an email notification when a new user registers on your site. Referring to the WP codex, you find the user_register action hook. This provides you with a $user_id variable, which you can use to get the user’s details. wp_mail() handles the rest:

add_action( 'user_register', 'notify_on_user_registration' );
function notify_on_user_registration( $user_id ) {
	
	// Get the user
	$user = get_user_by( 'id', $user_id );
	
	// Prepare email subject and body
	$subject = "New user registration";
	$message = "A new user {$user->first_name} {$user->last_name} has registered.";
	
	// Send the email
	wp_mail( '[email protected]', $subject, $message );
}

What if you want to redirect users to your frontpage on login? You achieve this easily with the login_redirect hook, which provides the URL to which users are redirected by default:

add_filter( 'login_redirect', 'redirect_user_on_login' );
function redirect_user_on_login( $redirect_to ) {
	return home_url();
}

Soon after you add this, you quickly realise that as an admin, you want to be taken straight to the WP dashboard instead of the homepage. Not a problem – you just amend the hook to work for admins only:

add_filter( 'login_redirect', 'redirect_user_on_login', 10, 3 );
function redirect_user_on_login( $redirect_to, $request, $user ) {
	// Change the login URL unless user is an administrator
	if ( isset( $user->roles ) && is_array( $user->roles ) ) {
		if ( in_array( 'administrator', $user->roles ) ) {
			return $redirect_to; // Redirect administrators to default (admin area)
		}
	}
	
	return $redirect_to;
}

Notice that $priority and $arguments have come into play!

Since you need access to the hook’s third argument to check the user’s level, you need to display 3 arguments; you pass a value of 3.

The $priority value of 10 is the same as default, of course, but is is specified in order to reach the $arguments value that you needed above.

Creating your own hooks

The power to create custom hooks is within your hands! The functions provided by WP look like this:

do_action( $hook_name, $arg_1, $arg_2 … $arg_n );

apply_filters( $hook_name, $arg_1, $arg_2 … $arg_n );

Again, they’re sort of similar. Both functions accept a name – this is where you name the hook. All the following arguments are the same arguments made available to the functions you add to that hook. apply_filters() must have at least one additional argument (the argument being filtered), but all others are optional.

For example, if you want to inject an analytics script right before your opening body tag, but don’t want to turn header.php into a mess:

	<?php do_action( 'before_body_tag' ); ?;>
</head>
<body>

Then you can add as many scripts as you need:

add_action( 'before_body_tag', 'add_analytics_custom' );
function add_analytics_custom() {
	// <script> tags and analytics code go here
}

apply_filters() works slightly differently. As discussed above, filters are used to modify values. Since filters return a value, you assign your apply_filters() call to a variable:

<?php $text = apply_filters( 'call_to_action_text', 'About us' ); ?>
<a href="<?php echo get_permalink( 'about' ); ?>" class="button cta">
	<?php echo $text; ?>
</a>

// Alternatively, you can echo the result of apply_filters() directly
<a href="<?php echo get_permalink( 'about' ); ?>" class="button cta">
	<?php echo apply_filters( 'call_to_action_text', 'About us' ); ?>
</a>

Then you can modify the filtered variable as necessary:

add_filter( 'call_to_action_text', 'change_call_to_action_text' );
function change_call_to_action_text( $text ) {
	if ( is_front_page() ) {
		$text = 'Discover more';
	}
	
	return $text;
}

The downsides

No system is perfect, and hooks are no exception. When it comes to core hooks, you generally have to make do with what you’re given. Very occasionally you’ll encounter situations where a hook isn’t in the most convenient place for you. There’s not much that can be done when this happens, so view it as an opportunity to find a creative solution!

Also, since hooks can exist anywhere within the WP directory structure, it’s sometimes tricky to find where certain logic is happening. This can be offset by separating your function hooks into their own files.

Further advice

  • When creating functions for hooks, ensure the name is unique, otherwise a conflict will occur. The same goes for your custom hook and filter names!
  • Use hooks to keep your templates uncluttered. A few generic do_action() hooks inserted throughout your templates can help keep logic away from markup (almost always a good idea).
  • When you install a new plugin, check out what hooks it provides and think about how you might utilise them. This is especially useful for the inevitable times that a plugin doesn’t quite do what you want.
  • When adding an action or filter, you can replace the function name with the function itself. This can help keep actions and filters self-contained. Functions added this way can’t be removed using remove_action() or remove_filter(), so be careful!
add_action( 'where_to_do_the_thing', function() {
	echo 'The thing is done.';
}, 10, 1 );
  • Check out the other hook-related functions available, such as did_action() or has_action(). You never know when just being aware of them will be useful.

Summary

Using hooks is a matter of habit. Try asking yourself two simple questions anytime you work on your WP project:

  1. “Is there a hook that’ll make this easier for me?”
  2. “Can I add a hook that’ll make this easier for future me?”

Once you approach WP development with this attitude, you’ll find many doors opening.

The examples in this guide are the tip of the iceberg. There are hundreds of hooks in WP core alone, to say nothing of the massive library of plugins and themes. Have a look at the WP action reference and filter reference for the full list of core WP hooks.

Leave a Reply

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