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 (or Ctrl + 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:

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.

Leave a Reply