Technote

Development workflow Intro

Series Using hooks and filters properly Part 1 of 8

Actions and filters: the return value is the whole difference

add_action and add_filter write into the same registry. Only one thing separates them: a filter must return a value, and one that forgets silently nulls the thing it filtered.

Hooks are the reason WordPress has stayed extensible for so long. You can change behaviour without editing core, forking a theme or patching somebody else’s plugin, which means updates do not overwrite your work. Extension and upgrading not blocking each other is not a common property in software.

There are only two kinds of hook: actions and filters. One thing separates them — whether a value comes back.

Actions do work; filters transform values

An action says “at this point, do something”. It prints, saves, sends mail. There is nothing to return, and anything you do return is discarded.

A filter says “take this value, change it, hand it back”. WordPress uses whatever apply_filters() returns, verbatim. The return is the contract.

// Action — does work. The return value is ignored.
add_action( 'wp_footer', 'wper_footer_note' );

function wper_footer_note() {
	echo '<p class="note">Get in touch any time.</p>';
}

// Filter — receives a value and must hand one back.
add_filter( 'excerpt_length', 'wper_excerpt_length' );

function wper_excerpt_length( $length ) {
	return 30;
}

Under the bonnet add_action() simply calls add_filter(), and both write into the same global registry. The registration machinery is identical; only whether the return value is used or thrown away differs. Two names exist for the reader, not the machine — so that the call site tells you whether a return is expected.

Forget the return and the value quietly disappears

Omit return in a filter callback and PHP hands back null. No error, no warning. That null is passed to the next callback in the chain and ends up on the page as nothing at all.

// A very common shape: only the guard clause returns
add_filter( 'the_title', 'wper_clean_title' );

function wper_clean_title( $title ) {
	if ( is_admin() ) {
		return $title;
	}

	$title = trim( $title );   // function ends here, so null goes back
}

The symptom is nasty. Titles look fine in the admin, because that branch does return, and only the front end loses them. So you start looking in the template or the theme.

Same registry, but the handling of the return value is what separates the two

You only receive the arguments you declare

The fourth parameter of add_filter() is how many arguments you accept, and it defaults to one. Even when a filter passes three, you get only the first unless you say otherwise.

// Declare 2 or $post_id never arrives.
add_filter( 'the_title', 'wper_tag_title', 10, 2 );

function wper_tag_title( $title, $post_id ) {
	return $title;
}

The third parameter is priority. Leave it at the default of 10 for now — when to change it, and why changing it is never a permanent fix, is the subject of part three.

Wider habits for hook-first development live in the development workflow archive, and the procedure we follow when applying these rules on somebody else’s theme and plugins is published on our process page.

Next part

Choosing the right hook is not enough if you attach at the wrong moment. The next part covers what must not happen before init — including the case where translations vanish without a single warning.

More on this topic

All technotes

Development workflow Practical

Turning taste arguments into rule checks

"It feels a bit cramped" can be neither argued with nor fixed. Spacing off the scale, colour off the palette, contrast below threshold, missing states — four rules…

Designers 9 min read

Development workflow Practical

Do not swap everything at once

A full swap makes every problem appear at the same moment — which means none of them can be attributed. So you switch one template at a time.

Designers 6 min read

Development workflow Practical

Adding an SCSS build, and whether to commit the output

WordPress themes are expected to deploy without a build step, which leads to the opposite conclusion from ordinary application code — and to its own costs.

Developers 7 min read

₩270,000 · Join the program