Hooks are how WordPress lets you change its behaviour without editing core, theme or plugin files. Almost everything in themes and plugins is built on them. There are two kinds: actions (do something at a certain moment) and filters (change a value before WordPress uses it).
Actions: run code at a point in time
add_action( 'wp_footer', function () {
echo '<!-- served by web1 -->';
} );
WordPress calls do_action( 'wp_footer' ) while rendering the page footer, and every function attached with add_action( 'wp_footer', … ) runs at that moment. Actions don’t return anything.
Filters: modify a value
add_filter( 'excerpt_length', function ( $length ) {
return 30; // words
} );
WordPress calls apply_filters( 'excerpt_length', 55 ), passes the value through every attached function, and uses the result. A filter must always return a value. Forgetting return is the most common bug, and silently empties the value.
Priority and arguments
add_filter( 'the_content', 'add_cta_box', 20, 1 );
function add_cta_box( $content ) {
if ( is_singular( 'post' ) && in_the_loop() && is_main_query() ) {
$content .= '<div class="cta">Need a server? <a href="/usa/">See plans</a></div>';
}
return $content;
}
- Priority (default 10): lower numbers run earlier. Use a higher number to run after other plugins.
- Accepted arguments (default 1): some hooks pass several values. Raise this number to receive them:
add_action( 'save_post', function ( $post_id, $post, $update ) {
// …
}, 10, 3 );
Commonly used hooks
| Hook | Type | Use |
|---|---|---|
init |
action | Register post types, taxonomies, shortcodes |
wp_enqueue_scripts |
action | Load CSS/JS on the front end |
admin_enqueue_scripts |
action | Load CSS/JS in wp-admin |
wp_head / wp_footer |
action | Output in the page head or footer |
save_post |
action | React when a post is saved |
template_redirect |
action | Redirects or access control before output |
the_content |
filter | Modify post content |
the_title |
filter | Modify titles |
body_class |
filter | Add CSS classes to <body> |
wp_mail |
filter | Change outgoing email parameters |
upload_mimes |
filter | Allow extra file types |
rest_api_init |
action | Register REST routes |
Removing someone else’s hook
To undo what a theme or plugin does, remove its callback with the same priority it was added with:
add_action( 'init', function () {
remove_action( 'wp_head', 'wp_generator' ); // hide WordPress version
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
} );
For class methods, you need the object instance (remove_action( 'hook', [ $instance, 'method' ] )), which some plugins expose through a global or a singleton.
Create your own hooks
If you write a plugin or theme, add hooks so others can extend it:
do_action( 'mysite_before_pricing_table', $plan_group );
$columns = apply_filters( 'mysite_pricing_columns', [ 'cpu', 'ram', 'disk' ], $plan_group );
Prefix hook names to avoid collisions.
Where to put hook code
- A site-specific plugin (
wp-content/plugins/site-tweaks/site-tweaks.php, or a must-use plugin inwp-content/mu-plugins/) for functionality that should survive theme changes. - The child theme’s
functions.phpfor presentation tweaks tied to that theme. See WordPress child theme. - Avoid “code snippets” in the database for anything important. Version control is better. See WordPress Git deployment.
Debugging hooks
- Query Monitor shows hooks fired on a page, and which callbacks are attached (Hooks & Actions panel).
- Check what’s attached in code:
global $wp_filter;
error_log( print_r( array_keys( $wp_filter['the_content']->callbacks ), true ) );
- Use
did_action( 'init' )to check whether an action has already run (adding a callback after the hook has fired does nothing).
See WordPress debug mode.
Performance notes
- Hooks that run on every request (
init,wp_head,the_contenton archive pages) should be cheap. Avoid remote HTTP calls or heavy queries there. - Cache expensive results with transients or the object cache. See Redis object cache.
Quick reference
| Task | Code |
|---|---|
| Run code at a moment | add_action( 'hook', $callback, $priority, $args ) |
| Change a value | add_filter( 'hook', $callback ), and return it |
| Remove a callback | remove_action/remove_filter( 'hook', $callback, $priority ) |
| Fire your own | do_action() / apply_filters() |
| Inspect | Query Monitor → Hooks |
Related: WordPress custom post types and WordPress plugin conflicts.