Problem: Hook fires, callback runs, something breaks
Error log: Fatal error on line 42
Line 42: return apply_filters('some_filter', $value);
Me staring at code: "Which of these 8 callbacks is causing this?!?"
Before Xdebug:
- Add
var_dump()everywhere - Reload page 50 times
- Still guessing
- 4 hours wasted!!
After Xdebug:
- Set breakpoint on line 42
- Step INTO filter callbacks
- See exact callback causing issue
- Fixed in 10 minutes!!
Here's how to debug WordPress PHP callbacks like a pro using Xdebug:
Why Xdebug For WordPress Callbacks?
WordPress = hook-based architecture
add_action('save_post', 'my_callback', 10);
add_filter('the_content', 'another_callback', 20);
add_action('wp_head', 'third_callback', 5);
Problem:
- 10 plugins hooking same action
- Which callback is breaking?
- Can't see execution order
- var_dump() only shows ONE callback!!
Xdebug solution:
- Set breakpoint on hook
- Step through EACH callback
- Inspect variables at each step
- See call stack
- No more guessing!!
Setup: Local + VS Code + Xdebug
Prerequisites
Install:
- Local by Flywheel (includes Xdebug built-in!)
- VS Code
- PHP Debug extension for VS Code
Local download: https://localwp.com/
Step 1: Enable Xdebug in Local
Open Local → Select your site → Bottom of page:
Toggle Xdebug ON
That's it!! Local handles all php.ini configuration!!
Verify Xdebug is running:
Create /wp-content/xdebug-test.php:
<?php
phpinfo();
Visit: yoursite.local/wp-content/xdebug-test.php
Search for "Xdebug" section → should show:
xdebug support: enabled
Version: 3.x
Step 2: Install PHP Debug Extension in VS Code
VS Code → Extensions → Search "PHP Debug"
Install: PHP Debug (by Xdebug)
Step 3: Configure VS Code
Open your site folder in VS Code
Click Debug icon (sidebar) → Create launch.json:
File: .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/app/public": "${workspaceRoot}"
},
"log": false
}
]
}
Key settings:
port: 9003 - Xdebug 3.x uses port 9003 (Xdebug 2.x used 9000)
pathMappings - Maps Local's internal path to your workspace
Step 4: Install Xdebug Browser Extension
Chrome: Xdebug Helper
Firefox: Xdebug Helper
Enable debugging:
- Click extension icon
- Select "Debug"
- Icon turns green ✅
This tells browser to send XDEBUG_SESSION cookie!!
Debugging WordPress Hooks
Example 1: Debug save_post Callbacks
Scenario: Post saves slowly, need to find which callback is slow
Code: wp-content/plugins/my-plugin/my-plugin.php
<?php
add_action('save_post', 'my_slow_callback', 10, 2);
function my_slow_callback($post_id, $post) {
// Set breakpoint HERE ↓
$data = get_post_meta($post_id, 'some_key', true);
// Slow API call
$result = wp_remote_get('https://api.example.com/data');
if (is_wp_error($result)) {
return;
}
update_post_meta($post_id, 'api_data', $result['body']);
}
Steps:
- Click left of line number to set breakpoint (red dot appears)
- VS Code → Debug panel → Click green play "Listen for Xdebug" (F5)
- Browser → Edit any post → Click "Update"
- VS Code highlights breakpoint line!!
Now you can:
Inspect variables:
- Hover over
$post_id→ see value - Hover over
$data→ see what was retrieved - Expand arrays/objects
Step through code:
- F10: Step Over (run line, don't enter functions)
- F11: Step Into (enter function calls)
- Shift+F11: Step Out (exit current function)
Check call stack:
- See entire execution path
- Trace back to what triggered this callback
Example 2: Multiple Callbacks on Same Hook
Problem: 5 plugins all hooking the_content, one breaks
Code:
<?php
// Plugin A
add_filter('the_content', 'plugin_a_content', 10);
// Plugin B
add_filter('the_content', 'plugin_b_content', 20);
// Plugin C
add_filter('the_content', 'plugin_c_content', 30);
Debug strategy:
Set breakpoint in WordPress core where filter fires:
File: wp-includes/plugin.php
Find apply_filters() function:
<?php
public function apply_filters($value, $args) {
// Set breakpoint HERE ↓
foreach ($this->callbacks as $priority => $callbacks) {
foreach ($callbacks as $callback) {
// Step INTO to see each callback
$value = call_user_func_array($callback['function'], $args);
}
}
return $value;
}
Now:
- F11 (Step Into) on
call_user_func_array() - Debugger jumps to actual callback function
- Inspect variables
- Continue to next callback
- Find exact callback causing issue!!
Example 3: Debugging AJAX Callbacks
Problem: AJAX request returns error, can't see why
Code:
<?php
add_action('wp_ajax_my_action', 'my_ajax_callback');
add_action('wp_ajax_nopriv_my_action', 'my_ajax_callback');
function my_ajax_callback() {
// Set breakpoint HERE ↓
$post_id = $_POST['post_id'];
if (!$post_id) {
wp_send_json_error('Missing post ID');
}
$data = get_post($post_id);
wp_send_json_success($data);
}
Trigger AJAX in browser console:
jQuery.post(ajaxurl, {
action: 'my_action',
post_id: 123
});
Debugger stops on breakpoint!!
Inspect:
-
$_POSTarray -
$post_idvalue -
$datacontent
Common AJAX issues found:
-
$_POSTempty (missing data in JS) - Wrong variable types
- Database queries failing
- Permission checks failing
Advanced: Conditional Breakpoints
Problem: Hook fires 100 times, only want to debug when $post_id == 456
Solution: Right-click breakpoint → Edit Breakpoint → Add condition
Condition:
$post_id == 456
Now debugger only stops when condition is TRUE!!
Other useful conditions:
// Only debug for specific user
get_current_user_id() == 1
// Only debug for specific post type
$post->post_type == 'product'
// Only debug when variable is empty
empty($data)
// Only debug on error
is_wp_error($result)
Debugging Hooks Execution Order
Problem: Callbacks running in wrong order
Use: Query Monitor + Xdebug combo
Install Query Monitor plugin
Query Monitor shows all hooks and priorities:
Hook: save_post
- my_callback (priority 10)
- another_callback (priority 20)
- third_callback (priority 5) ← runs FIRST!
Then:
Set breakpoint in each callback
See execution order in real-time!!
Common WordPress Debugging Scenarios
Scenario 1: Filter Returns NULL
Problem:
add_filter('the_title', 'my_title_filter');
function my_title_filter($title) {
// Forgot to return!!
$title = strtoupper($title);
}
Without Xdebug: All titles disappear, WTF?!
With Xdebug:
- Set breakpoint on
$title = strtoupper($title); - Step Over (F10)
- See function ends without return
- Instant diagnosis!!
Fix:
function my_title_filter($title) {
$title = strtoupper($title);
return $title; // ← ADD THIS!!
}
Scenario 2: Hook Priority Conflict
Code:
// Theme
add_action('init', 'theme_setup', 10);
// Plugin
add_action('init', 'plugin_setup', 10); // Same priority!!
Problem: Execution order undefined when priority is same
Debug:
Set breakpoint in both functions
See which runs first
Fix: Change priority
add_action('init', 'plugin_setup', 5); // Run before theme
Scenario 3: Callback Receives Wrong Arguments
Problem:
add_action('save_post', 'my_callback');
function my_callback($post_id) {
// Missing $post parameter!!
var_dump($post); // Undefined variable
}
Xdebug shows:
Function only receives 1 argument but hook passes 3:
do_action('save_post', $post_id, $post, $update);
Fix:
add_action('save_post', 'my_callback', 10, 3); // Accept 3 args!!
function my_callback($post_id, $post, $update) {
// Now we have all arguments
}
VS Code Debugging Panel Explained
When breakpoint hits:
Variables section:
- Locals: Current function variables
- Superglobals:
$_GET,$_POST,$_SERVER, etc. - Constants:
WP_DEBUG,ABSPATH, etc.
Watch section:
- Add custom expressions
- Example:
$post->post_status - Updates in real-time as you step through
Call Stack section:
- Shows execution path
- Click any frame to see that context
- Trace back to original trigger
Breakpoints section:
- All breakpoints listed
- Toggle on/off
- Conditional breakpoints marked
Debug Console:
- Run PHP expressions
- Example:
get_option('siteurl') - Evaluate during execution
Troubleshooting Xdebug
Breakpoints Not Hitting
Check:
- Xdebug enabled in Local? (toggle ON)
- Browser extension enabled? (green icon)
- VS Code listening? (F5 pressed)
- Port correct in launch.json? (9003 for Xdebug 3.x)
Verify connection:
Add to wp-config.php:
<?php
if (function_exists('xdebug_info')) {
error_log('Xdebug is loaded!');
}
Check error log → should see message
Path Mapping Issues
Problem: Breakpoints show as "unverified" (gray circle)
Cause: Path mismatch between Local and VS Code
Fix: Update pathMappings in launch.json
Local's internal path: /app/public
Your workspace: /Users/you/Local Sites/yoursite/app/public
Mapping:
"pathMappings": {
"/app/public": "${workspaceRoot}"
}
Test: Click breakpoint, should turn red
Performance Impact
Xdebug slows down PHP execution
Solution: Only enable when debugging
Local makes this easy → toggle ON/OFF
Pro tip: Use conditional breakpoints to reduce overhead
Bottom Line
Stop using var_dump() for WordPress debugging!!
Xdebug shows:
- Exact callback causing issue
- Variable values at each step
- Execution order
- Call stack
- Everything you need to fix bugs FAST!!
My debugging time:
Before Xdebug:
- Simple bug: 30 minutes
- Complex hook issue: 4 hours
- "WTF is happening?!" moments: Daily
After Xdebug:
- Simple bug: 5 minutes
- Complex hook issue: 30 minutes
- "WTF is happening?!" moments: GONE!!
Setup time: 10 minutes
Time saved: HUNDREDS of hours
Xdebug = WordPress developer superpower!! 🔥
This article contains affiliate links!


Top comments (1)
Been using Xdebug for 2 years now... used to waste hours with var_dumps trying to find which save_post callback was breaking. Now just set breakpoint, step into each callback, find issue in 5 minutes. Call stack feature is amazing for seeing full execution path. Pro tip: conditional breakpoints when hooks fire 100+ times 🔥