WordPress runs on Hooks (Actions and Filters). Sometimes, unexpected things happen – content disappears, titles change, styles break. You suspect a plugin is interfering, but which one?
You need to know exactly what functions are attached to a specific hook (e.g., the_content or wp_head).
The WordPress hook system: Understanding the foundation
WordPress’s hook system is what makes it extensible. Every major function in WordPress fires hooks, allowing plugins and themes to modify behavior without editing core files.
Two Types of Hooks:
- Actions – Do something at a specific point (e.g.,
wp_head,the_content) - Filters – Modify data before it’s used (e.g.,
the_title,the_content)
When debugging, you need to see:
- What functions are hooked
- Their priorities (execution order)
- Which plugin/theme added them
- The callback function names
The complete debugging snippet
Add this enhanced function to your functions.php or use it in a Must-Use plugin during development:
/**
* Inspect WordPress hooks with detailed information
*
* @param string $hook_name The hook to inspect (e.g., 'the_content', 'wp_head')
* @param bool $show_details Show full callback details
* @return void
*/
function wppoland_inspect_hook( $hook_name, $show_details = false ) {
global $wp_filter;
if ( ! isset( $wp_filter[ $hook_name ] ) ) {
echo '<div style="background:#fff3cd; border:2px solid #ffc107; padding:15px; margin:20px 0;">';
echo "<strong>Hook '$hook_name' has no attached functions.</strong>";
echo '</div>';
return;
}
$hook_data = $wp_filter[ $hook_name ];
echo '<div style="background:#fff; border:2px solid #dc3545; padding:20px; margin:20px 0; font-family:monospace; font-size:12px; max-width:100%; overflow-x:auto;">';
echo "<h2 style='margin-top:0; color:#dc3545;'>Debugging Hook: <code>$hook_name</code></h2>";
// Sort by priority
ksort( $hook_data->callbacks );
echo '<table style="width:100%; border-collapse:collapse;">';
echo '<thead><tr style="background:#f8f9fa;"><th style="padding:8px; text-align:left; border:1px solid #dee2e6;">Priority</th><th style="padding:8px; text-align:left; border:1px solid #dee2e6;">Function</th><th style="padding:8px; text-align:left; border:1px solid #dee2e6;">Source</th></tr></thead>';
echo '<tbody>';
foreach ( $hook_data->callbacks as $priority => $callbacks ) {
foreach ( $callbacks as $callback ) {
$function_name = 'Unknown';
$source_file = 'Unknown';
if ( is_string( $callback['function'] ) ) {
$function_name = $callback['function'];
if ( function_exists( $function_name ) ) {
$reflection = new ReflectionFunction( $function_name );
$source_file = $reflection->getFileName() . ':' . $reflection->getStartLine();
}
} elseif ( is_array( $callback['function'] ) ) {
if ( is_object( $callback['function'][0] ) ) {
$function_name = get_class( $callback['function'][0] ) . '::' . $callback['function'][1];
} else {
$function_name = $callback['function'][0] . '::' . $callback['function'][1];
}
try {
$reflection = new ReflectionMethod( $callback['function'][0], $callback['function'][1] );
$source_file = $reflection->getFileName() . ':' . $reflection->getStartLine();
} catch ( ReflectionException $e ) {
$source_file = 'Unable to determine';
}
} elseif ( is_object( $callback['function'] ) ) {
$function_name = 'Closure';
$reflection = new ReflectionFunction( $callback['function'] );
$source_file = $reflection->getFileName() . ':' . $reflection->getStartLine();
}
// Detect plugin/theme
$source_type = 'Core';
if ( strpos( $source_file, 'wp-content/plugins' ) !== false ) {
$source_type = 'Plugin';
preg_match( '/plugins\/([^\/]+)/', $source_file, $matches );
$plugin_name = isset( $matches[1] ) ? $matches[1] : 'Unknown';
} elseif ( strpos( $source_file, 'wp-content/themes' ) !== false ) {
$source_type = 'Theme';
preg_match( '/themes\/([^\/]+)/', $source_file, $matches );
$plugin_name = isset( $matches[1] ) ? $matches[1] : 'Unknown';
} else {
$plugin_name = 'WordPress';
}
echo '<tr style="border-bottom:1px solid #dee2e6;">';
echo '<td style="padding:8px; border:1px solid #dee2e6;"><strong>' . esc_html( $priority ) . '</strong></td>';
echo '<td style="padding:8px; border:1px solid #dee2e6;"><code>' . esc_html( $function_name ) . '</code></td>';
echo '<td style="padding:8px; border:1px solid #dee2e6;"><span style="color:' . ( $source_type === 'Plugin' ? '#dc3545' : ( $source_type === 'Theme' ? '#0073aa' : '#28a745' ) ) . ';">' . esc_html( $source_type ) . '</span> - ' . esc_html( $plugin_name ) . '</td>';
echo '</tr>';
if ( $show_details ) {
echo '<tr><td colspan="3" style="padding:4px 8px; font-size:11px; color:#666; border:1px solid #dee2e6;">';
echo 'File: ' . esc_html( $source_file );
echo '</td></tr>';
}
}
}
echo '</tbody></table>';
echo '</div>';
}
// Usage Examples:
// add_action( 'wp_footer', function(){ wppoland_inspect_hook('the_content'); } );
// add_action( 'wp_footer', function(){ wppoland_inspect_hook('wp_head', true); } ); // With details
How to use the debugging function
Basic usage
Add this to your functions.php temporarily:
// Inspect the_content hook
add_action( 'wp_footer', function() {
if ( current_user_can( 'manage_options' ) ) { // Only for admins
wppoland_inspect_hook( 'the_content' );
}
} );
Visit any page and scroll to the footer. You’ll see a detailed table showing all functions hooked to the_content.
Inspect multiple hooks
add_action( 'wp_footer', function() {
if ( ! current_user_can( 'manage_options' ) ) return;
$hooks_to_check = array( 'the_content', 'wp_head', 'the_title', 'excerpt_length' );
foreach ( $hooks_to_check as $hook ) {
wppoland_inspect_hook( $hook );
}
} );
Inspect with full details
// Show file paths and line numbers
add_action( 'wp_footer', function() {
if ( current_user_can( 'manage_options' ) ) {
wppoland_inspect_hook( 'the_content', true ); // true = show details
}
} );
Understanding the output
The output shows a table with three columns:
Priority column
WordPress executes hooks in priority order (lower numbers first). Default priority is 10.
Common Priorities:
1-9: Early execution (before default)10: Default priority11-99: Late execution (after default)999: Very late (almost last)
Example:
Priority 5: wpautop (adds paragraphs)
Priority 10: do_shortcode (processes shortcodes)
Priority 20: custom_plugin_function (runs last)
Function column
Shows the callback function name:
- String functions:
wpautop,do_shortcode - Class methods:
MyPlugin::process_content - Closures:
Closure(anonymous functions)
Source column
Indicates where the hook was added:
- Core: WordPress core functions
- Plugin: Name of the plugin
- Theme: Name of the theme
Common debugging scenarios
Scenario 1: Content disappears
Problem: Post content is empty or missing.
Debug:
wppoland_inspect_hook( 'the_content' );
Look for:
- Functions that return empty strings
- Functions with high priority that might override content
- Plugin functions that strip HTML
Scenario 2: Title changes unexpectedly
Problem: Page titles are modified by something.
Debug:
wppoland_inspect_hook( 'the_title' );
Look for:
- SEO plugins modifying titles
- Translation plugins
- Custom title filters
Scenario 3: Styles break
Problem: CSS/JS not loading correctly.
Debug:
wppoland_inspect_hook( 'wp_head' );
wppoland_inspect_hook( 'wp_enqueue_scripts' );
Look for:
- Plugins removing stylesheets
- Conflicting enqueue priorities
- Scripts loading in wrong order
Advanced: Programmatic hook inspection
Check if specific function is hooked
function wppoland_is_function_hooked( $hook_name, $function_name ) {
global $wp_filter;
if ( ! isset( $wp_filter[ $hook_name ] ) ) {
return false;
}
foreach ( $wp_filter[ $hook_name ]->callbacks as $priority => $callbacks ) {
foreach ( $callbacks as $callback ) {
if ( is_string( $callback['function'] ) && $callback['function'] === $function_name ) {
return array( 'priority' => $priority, 'found' => true );
}
if ( is_array( $callback['function'] ) && $callback['function'][1] === $function_name ) {
return array( 'priority' => $priority, 'found' => true );
}
}
}
return false;
}
// Usage
if ( wppoland_is_function_hooked( 'the_content', 'wpautop' ) ) {
echo 'wpautop is hooked to the_content';
}
Remove specific hook temporarily
// Remove a problematic hook
remove_filter( 'the_content', 'problematic_function', 10 );
// Or remove all hooks from a plugin
global $wp_filter;
if ( isset( $wp_filter['the_content'] ) ) {
foreach ( $wp_filter['the_content']->callbacks as $priority => $callbacks ) {
foreach ( $callbacks as $callback ) {
if ( strpos( $callback['function'], 'ProblemPlugin' ) !== false ) {
remove_filter( 'the_content', $callback['function'], $priority );
}
}
}
}
Modern alternative: Query monitor plugin
In 2026, the best way to debug hooks is using the Query Monitor plugin by John Blackbourn.
Why query monitor?
- Visual Interface: Clean, searchable table
- File Paths: Shows exact file locations
- Component Names: Identifies plugins/themes
- Performance Data: Shows execution time per hook
- No Code Required: GUI-based debugging
Installation
## Via wp-CLI
wp plugin install query-monitor --activate
## Or download from WordPress.org
Using query monitor
- Install and activate Query Monitor
- Visit any page on your site
- Look for the “QM” icon in the admin bar
- Click “Hooks & Actions”
- Search for your hook name
- See all attached functions with priorities and sources
Query monitor vs. Custom snippet
Use Query Monitor when:
- You need a permanent debugging solution
- You want performance metrics
- You’re debugging multiple hooks
- You prefer GUI over code
Use Custom Snippet when:
- You need quick, one-off debugging
- You’re in a development environment
- You want to programmatically check hooks
- Query Monitor isn’t available
Best practices for hook debugging
1. Only debug IN development
Never leave debugging code in production:
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
add_action( 'wp_footer', function() {
wppoland_inspect_hook( 'the_content' );
} );
}
2. Restrict to admins
Always check user capabilities:
if ( current_user_can( 'manage_options' ) ) {
// Show debug info
}
3. Use descriptive hook names
When creating custom hooks, use prefixes:
// Good
do_action( 'wppoland_before_content' );
// Bad
do_action( 'before_content' );
4. Document your hooks
/**
* Fires before the main content area
*
* @param string $content The post content
*/
do_action( 'wppoland_before_content', $content );
Troubleshooting common issues
Issue: Hook not showing up
Possible Causes:
- Hook hasn’t fired yet (check hook timing)
- Conditional logic prevents hook from firing
- Hook name is misspelled
Solution:
// Check if hook exists
if ( has_action( 'your_hook_name' ) ) {
echo 'Hook exists';
} else {
echo 'Hook does not exist';
}
Issue: Function not executing
Possible Causes:
- Priority conflict
- Conditional logic
- Hook removed by another plugin
Solution:
// Check current priority
$priority = has_filter( 'the_content', 'your_function' );
echo "Priority: $priority";
Summary
Debugging WordPress hooks is essential for understanding plugin conflicts and theme issues. The custom snippet provides detailed information about:
- What functions are hooked
- Their execution priorities
- Which plugins/themes added them
- Source file locations
Key Takeaways:
- Use
$wp_filterglobal to inspect hooks - Query Monitor is the best GUI solution for 2026
- Always restrict debugging to development/admin users
- Understand priorities to fix execution order issues
- Document your custom hooks for maintainability
Whether you use the custom snippet or Query Monitor, understanding WordPress hooks is crucial for professional WordPress development in 2026.

