Stepping Through Worker Code
When your Cloudflare Worker isn't behaving as expected — whether due to a race condition, an unexpected input, or a logic error — you need to see exactly what the code is doing at each step. While console.log statements and log streams give you a high-level view, breakpoint debugging lets you pause execution at a specific line and inspect the state of your application.
Cloudflare Workers now support breakpoint debugging across local development environments. You can launch a debugger session from wrangler dev by pressing [d], which opens a DevTools instance. The same editor is also available from the Cloudflare Dashboard and the Workers Playground. For IDE-based workflows, both VSCode and WebStorm are supported.
VSCode Setup
To debug with VSCode, create a .vscode/launch.json file in your project with the appropriate Wrangler configuration:
{
"configurations": [
{
"name": "Wrangler",
"type": "node",
"request": "attach",
"port": 9229,
"cwd": "/",
"resolveSourceMapLocations": null,
"attachExistingChildren": false,
"autoAttachChildProcesses": false
}
]
}
After saving this configuration, open your project in VSCode, start a terminal, and run npx wrangler dev to spin up the local dev server. In the Run & Debug panel, select the Wrangler configuration and click the play icon. You should see Wrangler: Remote Process [0] appear in the Call Stack panel.
Open a .js or .ts file, set at least one breakpoint, then visit the Worker's local URL (default http://127.0.0.1:8787) in your browser. The breakpoint will trigger, and you can inspect variables and step through the code directly in the editor.
WebStorm Setup
For WebStorm, create a new Attach to Node.js/Chrome Debug Configuration and set the port to 9229:

Start the local dev server with npx wrangler dev, then launch the debug configuration:

Add a breakpoint to your source file and navigate to the Worker's local URL (default http://127.0.0.1:8787) in your browser. The debugger will pause at the breakpoint, giving you full visibility into the code's execution context.
Adding Debugger Support to workerd
Under the hood, both workerd and the Cloudflare Workers runtime embed V8 to execute JavaScript and WASM. V8 exposes several Chrome DevTools Protocol (CDP) domains to embedding applications, including Runtime, Profiler, HeapProfiler, and — newly supported in workerd — Debugger. The Debugger domain provides the commands needed to set breakpoints, step through code, and receive events when execution pauses.
Implementing the Debugger domain in workerd presented a specific challenge. V8 expects breakpoint suspension to happen inside a method that the embedder implements, which V8 calls when a breakpoint is hit. This method runs within the same event handler that is executing JavaScript. But workerd uses a single-threaded, event-driven model: that same thread must also process incoming network messages, including CDP commands from the debugger client. If a developer hits a breakpoint and then asks the debugger to resume execution, that resume command cannot be delivered to the executing thread because the thread is suspended inside the breakpoint and cannot return to the event loop to read the message.
Workerd solves this by adding a dedicated I/O thread. This thread handles the sending and receiving of all CDP messages while the JavaScript thread may be paused at a breakpoint or a debugger statement. When CDP commands arrive, the I/O thread wakes the JavaScript thread and manages the response path back to the debugger client. The synchronization between the two threads required careful design to prevent dropped messages, but the architecture is straightforward: the I/O thread keeps communication flowing even when the executing thread is paused.
Source Maps and Remote Debugging
For debugging to be useful, V8 must be able to show developers their original source code rather than generated or minified JavaScript. V8 locates original sources through source maps, which are referenced via two special comments in the JavaScript being executed:
//# sourceMappingURL=generated.js.map
//# sourceURL=file:///absolute/path/to/generated.js
The source map URL is resolved relative to the source URL. The map itself contains entries that tell V8 how lines in the generated code correspond to lines in the original source files:
{
"version": 3,
"sources": ["../src/index.ts"],
"sourcesContent": ["interface Env { ... }\n\nexport default ..."],
"mappings": ";AAIA,IAAO,mBAA8B;AAAA,EACjC,MAAM,MAAM,SAAS,KAAK,KAAK;...",
"names": []
}
When DevTools enables the Debugger domain, V8 sends information about all parsed scripts, including the fully-qualified source map URL. In this example, that URL would be file:///absolute/path/to/generated.js.map. DevTools needs to fetch that URL to perform source mapping. However, there's a wrinkle: Cloudflare's patched DevTools is hosted at https://devtools.devprod.cloudflare.dev/, and browsers will not allow JavaScript running on that origin to fetch file:// URLs. But file:// URLs are required so that IDEs like VSCode can match source-mapped files to files on disk.
To work around this, Wrangler's inspector proxy rewrites the CDP script-parsed messages that V8 sends. When the inspector WebSocket handshake's User-Agent indicates the client is a browser, the proxy swaps the protocol used in the source URLs. This allows the browser-based DevTools to fetch source maps while VSCode and other IDEs still receive the file:// URLs they need.

Once the connection is established and source maps are loaded, setting a breakpoint works as expected. When you set a breakpoint in an original source file, DevTools uses the source map's mappings field to translate that location into the corresponding position in the generated JavaScript, and registers the breakpoint there. When V8 hits that line, DevTools pauses and displays the original source location. Stepping through the code performs the same translation in reverse for each step: the current segment in original source is mapped to generated code, the step command is sent to V8, and the new paused location is mapped back to original source for display.
What’s Next: Launching Debug Sessions
Both the Visual Studio Code and WebStorm debugging setups currently require you to attach to a dev server that is already running. The natural next step is for the IDE to launch the dev server itself and attach automatically.
When debugging Node.js programs, Visual Studio Code and WebStorm inject a --require hook into the NODE_OPTIONS environment variable. That hook registers the process’s inspector URL with the editor over a well-known socket, so if the Node.js process spawns child processes—like when running npm scripts via the JavaScript Debug Terminal—the editor can debug those children too.
The plan is to detect that same --require hook in workerd child processes started by Wrangler and Miniflare. That way, debugging an npm launch task becomes seamless, without the manual step of starting a dev server and attaching a debugger to it.
Ready Today
All the debugging capabilities described here are available now. Logs and DevTools work either through the Cloudflare dashboard or with Wrangler, the command-line tool for the Cloudflare Developer Platform. Breakpoint debugging and Node-style logging ship in the latest Wrangler release and are enabled by running npx wrangler@latest dev in a terminal.
Feedback is welcome in the #wrangler channel on the Cloudflare Developers Discord. If you hit unexpected behavior, please open a GitHub issue.



