In Part 3, I showed you how to test queues, cron, and HTTP handlers locally. But here's the question I kept asking: can I actually debug this stuff with breakpoints?
Short answer: Yes, absolutely. And it works better than I expected.
Let me show you how to set it up.
The Setup: VS Code + Wrangler Dev + Debugger
When you run wrangler dev, it starts a Node.js process that runs your Worker. VS Code's debugger can attach to that process, which means you can:
- ✅ Set breakpoints in your
queue()handler - ✅ Set breakpoints in your
scheduled()handler - ✅ Set breakpoints in your
fetch()handler - ✅ Inspect variables, step through code, everything
Here's how to set it up.
Step 1: Run Wrangler in Debug Mode
Start Wrangler with the inspect flag:
wrangler dev --remote=false --inspect
Important flags:
-
--remote=false- runs locally (not in Cloudflare's dev environment) -
--inspect- enables the Node.js debugger on port 9229
You should see output like:
Debugger listening on ws://127.0.0.1:9229/...
For help, see: https://nodejs.org/en/docs/inspector
⎔ Starting local server...
[wrangler:inf] Ready on http://localhost:8787
That Debugger listening line means you're good to go.
Step 2: Configure VS Code
Create or update .vscode/launch.json in your project:
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Wrangler",
"type": "node",
"request": "attach",
"port": 9229,
"skipFiles": ["<node_internals>/**"],
"sourceMaps": true,
"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"!**/node_modules/**"
]
}
]
}
What this does:
- Attaches VS Code's debugger to the Wrangler process
- Skips Node.js internal files (you don't want to debug Node itself)
- Enables source maps so breakpoints work with TypeScript
Step 3: Start Debugging
- Run Wrangler in a terminal:
wrangler dev --remote=false --inspect
Open VS Code's Debug panel (
Cmd+Shift+Don Mac,Ctrl+Shift+Don Windows)Select "Attach to Wrangler" from the dropdown
Press the green play button (or press
F5)
You should see "Debugger attached" in your terminal, and VS Code's debug toolbar appears.
Step 4: Set Breakpoints and Trigger Queue Messages
Now the magic happens. Let's debug a queue handler:
// src/index.ts
export default {
async queue(batch, env, ctx) {
console.log(`[queue] Received ${batch.messages.length} messages`);
for (const message of batch.messages) {
// 👈 Set a breakpoint on this line
const { type, data } = message.body;
console.log(`[queue] Processing ${type}`);
// 👈 And another one here
await processJob(env, data);
message.ack();
}
}
};
Set breakpoints:
- Click in the gutter (left of line numbers) in VS Code
- A red dot appears - that's your breakpoint
Trigger a queue message:
wrangler queues send my-jobs '{"type": "test", "data": "hello"}'
🎉 Your breakpoint hits!
VS Code pauses execution, and you can:
- Inspect
message.body - Check the value of
envbindings - Step through the code line by line
- See the call stack
This is incredibly useful for debugging complex queue logic.
Debugging All Three Handler Types
HTTP Handlers (fetch)
Set a breakpoint in your fetch handler:
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// 👈 Breakpoint here
if (url.pathname === '/api/trigger') {
await env.JOB_QUEUE.send({ type: 'manual' });
return new Response('Queued');
}
}
};
Then hit the endpoint:
curl http://localhost:8787/api/trigger
The debugger pauses, and you can inspect the request object, headers, etc.
Scheduled Handlers (scheduled)
Set a breakpoint in your scheduled handler:
export default {
async scheduled(event, env, ctx) {
// 👈 Breakpoint here
const time = new Date(event.scheduledTime);
console.log('[scheduled] Running at', time);
await env.JOB_QUEUE.send({ type: 'cron' });
}
};
Trigger it by pressing s in the Wrangler terminal (or use the /__scheduled endpoint).
The debugger pauses, and you can see the event.scheduledTime value.
Queue Handlers (queue)
We already covered this above, but here's a more complex example:
export default {
async queue(batch, env, ctx) {
for (const message of batch.messages) {
try {
// 👈 Breakpoint here to inspect message structure
const job = parseJobMessage(message.body);
// 👈 Another breakpoint to step through processing
await processJob(env, job);
// 👈 Breakpoint to verify ack is called
message.ack();
} catch (error) {
// 👈 Breakpoint to debug failures
console.error('Job failed:', error);
message.retry();
}
}
}
};
Send messages:
wrangler queues send my-jobs '{"id": 1}'
wrangler queues send my-jobs '{"id": 2}'
You can step through each message in the batch and see exactly what's happening.
Advanced Debugging Techniques
1. Conditional Breakpoints
Right-click a breakpoint in VS Code → "Edit Breakpoint" → "Expression"
message.body.id === 42
Now the breakpoint only hits for messages with id: 42. Super useful when debugging batches.
2. Logpoints
Don't want to stop execution? Use a logpoint instead:
Right-click → "Add Logpoint"
Message {message.id} has data: {message.body}
This logs without pausing, like console.log but without modifying your code.
3. Watch Expressions
In VS Code's Debug panel, add Watch expressions:
message.attempts
env.DATABASE_URL
batch.messages.length
These update automatically as you step through code.
4. Debug Console
While paused, you can run code in the Debug Console:
// Check if a binding exists
> env.MY_QUEUE
Queue { ... }
// Inspect message body
> JSON.stringify(message.body, null, 2)
{
"type": "test",
"data": "hello"
}
// Test a function
> await parseJobMessage(message.body)
{ ... }
This is amazing for exploring objects and testing fixes without restarting.
Common Issues and Solutions
Issue 1: Breakpoints Show as Gray/Unverified
Problem: Source maps aren't working correctly.
Solution: Make sure your tsconfig.json has source maps enabled:
{
"compilerOptions": {
"sourceMap": true,
"inlineSourceMap": false
}
}
And restart both Wrangler and the VS Code debugger.
Issue 2: "Cannot connect to runtime process"
Problem: Wrangler isn't running with --inspect.
Solution: Make sure you started Wrangler with:
wrangler dev --remote=false --inspect
Issue 3: Debugger Disconnects After First Request
Problem: This can happen with certain Wrangler versions.
Solution: Use --persist-to to keep the Worker alive:
wrangler dev --remote=false --inspect --persist-to=.wrangler/state
Issue 4: Can't Debug External Dependencies
Problem: Breakpoints in node_modules don't work.
Solution: Remove this line from launch.json:
"skipFiles": ["<node_internals>/**"]
But be warned - you'll hit a LOT of Node.js internal breakpoints.
My Debugging Workflow
Here's how I debug a typical queue handler issue:
1. Reproduce the Issue Locally
# Terminal 1: Start Wrangler
wrangler dev --remote=false --inspect
# Terminal 2: Send problematic message
wrangler queues send my-jobs '{"id": 123, "type": "problematic"}'
2. Attach Debugger
- Open VS Code
- Press
F5to attach - Set breakpoints in the queue handler
3. Re-trigger
# Send the same message again
wrangler queues send my-jobs '{"id": 123, "type": "problematic"}'
Debugger hits, and I can step through to find the issue.
4. Test the Fix
Fix the code, save (Wrangler hot-reloads), trigger again:
wrangler queues send my-jobs '{"id": 123, "type": "problematic"}'
If it works, remove breakpoints and move on.
Pro Tips
Use Multiple Launch Configurations
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Wrangler (Local)",
"type": "node",
"request": "attach",
"port": 9229
},
{
"name": "Attach to Wrangler (Remote)",
"type": "node",
"request": "attach",
"port": 9229,
"localRoot": "${workspaceFolder}",
"remoteRoot": "/tmp/worker"
}
]
}
Switch between them based on whether you're using --remote=false or not.
Create a Debug Script
Add to package.json:
{
"scripts": {
"dev": "wrangler dev",
"dev:debug": "wrangler dev --remote=false --inspect",
"test:queue": "wrangler queues send my-jobs '{\"type\": \"test\"}'"
}
}
Now you can just run:
npm run dev:debug
Use Debugger Statements
Sometimes I just add debugger; directly in the code:
async queue(batch, env, ctx) {
for (const message of batch.messages) {
if (message.body.id === 42) {
debugger; // Automatic breakpoint when debugger is attached
}
await processJob(message.body);
message.ack();
}
}
This is faster than clicking around in VS Code when you know exactly where to break.
Debugging Production Issues Locally
Sometimes you get a failed message in production and want to replay it locally:
1. Get the Message from Your DLQ
# List messages in your dead letter queue
wrangler queues messages list my-jobs-dlq
# Copy the message body
2. Replay it Locally
# Paste the exact production message
wrangler queues send my-jobs '{"id": 123, "data": "..."}'
3. Debug With Real Data
Now you're debugging with the actual message that failed, which makes finding bugs way easier.
Performance Debugging
You can also profile your queue handlers:
export default {
async queue(batch, env, ctx) {
const start = performance.now();
for (const message of batch.messages) {
const jobStart = performance.now();
await processJob(message.body);
const jobTime = performance.now() - jobStart;
console.log(`Job ${message.id} took ${jobTime}ms`);
message.ack();
}
const totalTime = performance.now() - start;
console.log(`Batch of ${batch.messages.length} took ${totalTime}ms`);
}
};
Set a breakpoint after the loop and inspect totalTime to see if you're hitting performance issues.
Wrapping Up
Yes, you can absolutely debug queue handlers with VS Code.
In fact, you can debug all three handler types (HTTP, cron, queue) with full breakpoint support, variable inspection, and step-through debugging.
The setup is:
- Run
wrangler dev --remote=false --inspect - Configure VS Code's
launch.jsonto attach on port 9229 - Set breakpoints and trigger your handlers
- Debug like any other Node.js app
This has completely changed how I develop Workers. I rarely use console.log debugging anymore because breakpoints are so much faster.
Questions? Drop them in the comments! What's your debugging workflow like?
Quick Reference
# Start Wrangler in debug mode
wrangler dev --remote=false --inspect
# Send test queue message
wrangler queues send my-jobs '{"test": true}'
# Trigger scheduled event
# Press 's' in Wrangler terminal
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Wrangler",
"type": "node",
"request": "attach",
"port": 9229,
"skipFiles": ["<node_internals>/**"]
}
]
}
Happy debugging!
Top comments (0)