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.
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.