Introduction: The Hook Headache
If you’ve worked with WordPress long enough, you’ve probably fallen into the “Why is this running twice?” rabbit hole. Hooks — both actions and filters — are powerful, but once you have a dozen plugins and a custom theme all firing on init, chaos is inevitable.
Debugging hooks isn’t just about finding what broke — it’s about understanding when and why something executes. Over time, I’ve developed a calm, systematic approach that keeps me from spiraling into hook madness.
Step 1: Start With doing_action() or doing_filter()
Before digging through files, I check what’s running right now:
if ( doing_action( 'init' ) ) {
error_log( 'We’re inside init hook.' );
}
This simple check confirms your current execution stage. It’s my first sanity check when a function runs too early or too late.
Step 2: Use has_action() and has_filter() Like X-Ray Glasses
Ever wonder if a function is actually hooked? I use:
if ( has_action( 'wp_head', 'my_theme_function' ) ) {
error_log( 'Function is hooked into wp_head.' );
}
You can even check priority by printing:
error_log( has_action( 'wp_head', 'my_theme_function' ) );
This tells you where in the chain it’s firing. Priorities often cause those “why is this overriding my output?” moments.
Step 3: Print All Hooks on a Specific Action
When I really need to see what’s cooking, I dump everything:
global $wp_filter;
print_r( $wp_filter['init'] );
This will list all callbacks, their priorities, and even their source files (if you inspect deeply). It’s like lifting the hood on the WordPress engine.
For better readability, I use:
foreach ( $wp_filter['init']->callbacks as $priority => $functions ) {
foreach ( $functions as $function_name => $callback ) {
error_log( "Priority $priority: " . print_r( $function_name, true ) );
}
}
This gives a clear, chronological order of execution.
Step 4: Log Everything (But Cleanly)
Dumping to the browser can break output, so I prefer the error log:
error_log( 'Hook executed: ' . current_filter() );
And to make life easier, I use a helper:
function log_hook($message) {
error_log('[Hook Debug] ' . $message);
}
When debugging large projects, I add:
log_hook( current_filter() . ' fired by ' . wp_debug_backtrace_summary() );
Now I know who called what — priceless during plugin conflicts.
Step 5: Temporarily Override a Hook
Sometimes I just want to stop something from running to see what happens.
Instead of deleting code, I do:
remove_action( 'init', 'annoying_plugin_function', 10 );
Then test. If behavior changes, I know I’ve found the culprit.
(Pro tip: wrap this in a conditional to keep things tidy.)
Step 6: Visualize Hook Flow
When things get messy, I use tools like:
-
Query Monitor → see hooked functions, priorities, and timings.
-
Debug Bar + Debug Bar Actions and Filters Add-on → shows running hooks live.
-
Hook List Generator → outputs all hooks into an HTML table for quick scanning.
These visual tools help me spot redundant or misordered hooks quickly.
Step 7: Trace the Source (The Human Way)
If I find a suspicious function like my_plugin_modify_title, I run:
grep -R "my_plugin_modify_title" wp-content/
Or inside VS Code:
-
Hit
Cmd + Shift + F(orCtrl + Shift + F) -
Search the function name → instant file location.
This beats scrolling through dozens of plugin folders by hand.
Step 8: Use Breakpoints (When All Else Fails)
If I’m deep into a nightmare of filters inside filters, I switch to Xdebug or Ray:
ray(current_filter());
or just step through with breakpoints in PhpStorm.
Yes, it’s heavier than logs — but sometimes you need to watch the dominoes fall in real-time.
Step 9: Document What You Discover
Once I fix an issue, I always add inline comments like:
// Hooked at priority 20 because plugin XYZ runs at 15 and overrides it.
Your future self will thank you for this.
Final Thoughts
Debugging complex WordPress hooks isn’t about speed — it’s about patience and structure.
The real trick is staying calm when everything feels tangled. The moment you start tracing systematically — from the outside in — things start to make sense.
And next time your output disappears into the void, remember:
you’re not losing your mind — you’re just inside the hook labyrinth.