launch.json Explained: VS Code Debug Configurations for Node, Python, and Chrome
Last updated:
launch.json lives in .vscode/ and defines the debug configurations consumed by VS Code's Run and Debug panel. The file has two top-level keys — version (always "0.2.0") and a configurations array — and each configuration declares a debug type (node, python, chrome, msedge, pwa-node, lldb, ...) plus a request of launch or attach. Press F5 to run the selected entry.
Need to validate your launch.json for syntax errors? Paste it into Jsonic's JSON Validator — it pinpoints problems with line and column numbers (with JSONC mode for VS Code-style comments).
Where launch.json lives and the minimum file
VS Code looks for launch.json at .vscode/launch.json relative to each workspace folder. In a multi-root workspace, every folder can have its own. The Run and Debug panel merges all configurations into one dropdown.
The minimum legal file has a fixed schema version and at least one configuration:
{
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Launch current file",
"program": "${file}"
}
]
}The version string is a schema marker — it has always been "0.2.0" since VS Code 1.0 and is not the version of your project. Every configuration must have type, request, and name; everything else depends on the debug adapter.
You can keep this file out of source control, but most teams commit it — it captures tribal knowledge about how to debug the codebase and is far cheaper than a README.
launch vs attach: starting a new process vs connecting to a running one
Every configuration sets "request" to one of two values. The choice determines which other fields are meaningful.
| Aspect | "request": "launch" | "request": "attach" |
|---|---|---|
| What it does | VS Code spawns the program under the debugger | VS Code connects to a process you already started |
| Key fields | program, args, cwd, env, runtimeExecutable | port, address, processId, localRoot/remoteRoot |
| Environment variables | Settable via env / envFile | Fixed at process start time — can't change |
| Typical use | Local app, single script, test runner | Docker, remote SSH, already-running dev server |
| Auto-restart | "restart": true on crash | "restart": true re-attaches after disconnect |
Use launch when you control the start command. Use attach when the process runs somewhere you don't — Docker, a remote host, a long-running dev server you don't want to restart, or a child process forked by another tool.
Debugging Node.js: pwa-node, runtimeArgs, env, skipFiles
For Node.js, use "type": "pwa-node" (the modern JavaScript Debugger that ships built-in with VS Code). The legacy "node" type is auto-rewritten to pwa-node on save.
{
"type": "pwa-node",
"request": "launch",
"name": "Launch server",
"program": "${workspaceFolder}/src/server.ts",
"runtimeExecutable": "tsx",
"runtimeArgs": ["--no-warnings"],
"args": ["--port", "3000"],
"cwd": "${workspaceFolder}",
"env": {
"NODE_ENV": "development",
"DEBUG": "app:*"
},
"envFile": "${workspaceFolder}/.env",
"skipFiles": [
"<node_internals>/**",
"${workspaceFolder}/node_modules/**"
],
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"sourceMaps": true,
"console": "integratedTerminal"
}Field-by-field: program is the entry file; runtimeExecutable overrides the default node binary (use tsx, bun, or deno if needed); runtimeArgs are flags before the program path (e.g. --inspect-brk, --loader);args are passed to your program. skipFiles tells the debugger which globs to skip when stepping — always include "<node_internals>/**" so Step Into doesn't dive into Node's C++ shim code. outFiles is critical when source-mapping compiled output — point it at every directory that contains generated .js files.
For an attach config (process already running with --inspect=9229):
{
"type": "pwa-node",
"request": "attach",
"name": "Attach to running Node",
"port": 9229,
"restart": true,
"skipFiles": ["<node_internals>/**"]
}Debugging Python: type 'python', module vs program, justMyCode, env
The Microsoft Python extension provides "type": "python" (or "debugpy" in newer versions — both work). Choose between program (a file path) and module (a module name run via python -m).
{
"type": "python",
"request": "launch",
"name": "Run FastAPI",
"module": "uvicorn",
"args": ["app.main:app", "--reload", "--port", "8000"],
"cwd": "${workspaceFolder}",
"env": {
"PYTHONPATH": "${workspaceFolder}/src",
"LOG_LEVEL": "DEBUG"
},
"envFile": "${workspaceFolder}/.env",
"justMyCode": true,
"django": false,
"console": "integratedTerminal"
}justMyCode (default true) tells debugpy to skip stepping into library code under site-packages. Set it to false when debugging an issue you suspect is in a third-party package. module is the right choice for anything launched via python -m (Uvicorn, FastAPI, pytest, flask). Use program for plain script files: "program": "${file}" debugs the file currently open in the editor.
For pytest debugging, the Python extension exposes "purpose": ["debug-test"] configurations or the dedicated "Debug Test" code lens — usually faster than hand-rolling a launch entry.
Debugging browsers: type 'chrome' / 'edge', webRoot, sourceMaps, url
For browser debugging, use "type": "chrome" or "type": "msedge" (both ship in the built-in JavaScript Debugger; older configs may show pwa-chrome/pwa-msedge, which are aliases). The debugger speaks the Chrome DevTools Protocol over a local port.
{
"type": "chrome",
"request": "launch",
"name": "Debug React app",
"url": "http://localhost:5173",
"webRoot": "${workspaceFolder}/src",
"sourceMaps": true,
"sourceMapPathOverrides": {
"webpack:///./src/*": "${webRoot}/*"
},
"userDataDir": false,
"runtimeArgs": ["--auto-open-devtools-for-tabs"]
}url is what Chrome navigates to on launch — typically your dev server.webRoot tells the debugger where the source files live on disk so it can map URLs back to the editor. Get this wrong and breakpoints show as unbound.sourceMapPathOverrides is the fix when source maps embed paths like webpack:///./src/index.tsx that the debugger can't resolve verbatim — Vite and webpack each use a different convention.
For an attach config (Chrome started with --remote-debugging-port=9222):
{
"type": "chrome",
"request": "attach",
"name": "Attach to Chrome",
"port": 9222,
"url": "http://localhost:5173/*",
"webRoot": "${workspaceFolder}"
}userDataDir: false reuses your real Chrome profile (extensions, logins); the default creates a fresh isolated profile under .vscode/.chrome. The wildcard in url matches any route on the host.
Compound configurations: launch a backend and frontend together
The top-level "compounds" array groups existing configurations so a single F5 press starts all of them. Useful for full-stack apps where you want to debug a Node backend and a Chrome frontend in the same session.
{
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Server",
"program": "${workspaceFolder}/server/index.ts",
"runtimeExecutable": "tsx"
},
{
"type": "chrome",
"request": "launch",
"name": "Client",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/client/src"
}
],
"compounds": [
{
"name": "Full stack",
"configurations": ["Server", "Client"],
"stopAll": true,
"presentation": { "group": "fullstack", "order": 1 }
}
]
}stopAll: true stops every member when one stops — convenient for tearing down both halves on Shift+F5. presentation.group groups compounds in the Run dropdown so the picker stays readable as the list grows. A compound can reference configurations from any workspace folder in a multi-root setup by qualifying the name as "folder/Configuration Name".
Variables: ${workspaceFolder}, ${file}, ${env:VAR}, ${input:name}
VS Code substitutes ${name} tokens inside string values before passing them to the debug adapter. Most fields accept variables; a few (e.g. type) do not. The full reference:
| Variable | Resolves to |
|---|---|
${workspaceFolder} | Absolute path of the workspace root |
${workspaceFolderBasename} | Just the folder name (last path segment) |
${file} | Absolute path of the currently focused editor file |
${relativeFile} | ${file} relative to ${workspaceFolder} |
${fileBasename} | File name only (e.g. index.ts) |
${fileBasenameNoExtension} | File name without extension |
${fileDirname} | Directory containing the current file |
${cwd} | VS Code process working directory at startup |
${env:NAME} | Value of host environment variable NAME |
${config:section.key} | Value from settings.json (e.g. ${config:python.pythonPath}) |
${input:id} | Prompt the user; defined in top-level inputs array |
${command:id} | Run a VS Code command and use its return value |
${pathSeparator} | / on macOS/Linux, \\ on Windows |
inputs lets you prompt for a value on every F5 — handy for picking an environment, a test name, or a feature flag without editing the JSON:
{
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Run with env",
"program": "${workspaceFolder}/src/app.ts",
"env": { "STAGE": "${input:stage}" }
}
],
"inputs": [
{
"id": "stage",
"type": "pickString",
"description": "Which stage?",
"options": ["dev", "staging", "prod"],
"default": "dev"
}
]
}Input types: promptString (free text), pickString (dropdown from options), and command (run a VS Code command and use its return value, e.g. to list available test files).
Key terms
- launch.json
- VS Code's debug-configuration file, stored at
.vscode/launch.json. JSONC format with aversionstring of"0.2.0"and aconfigurationsarray consumed by the Run and Debug panel. - Debug adapter
- A small program that translates between VS Code's Debug Adapter Protocol and a specific runtime's debug API (V8 Inspector, debugpy, lldb, etc.). The
typefield in a configuration picks which adapter to use. - Breakpoint
- A marker that pauses execution at a source line. VS Code shows three states: bound (red, filled — adapter mapped it to a real loaded location), unbound (grey, hollow — set in the editor but not yet mapped), and conditional (red with a small dot — hits only when an expression is true).
- pwa-node
- The current built-in Node.js debug adapter (JavaScript Debugger / vscode-js-debug). Replaces the legacy
nodetype. Faster, handles worker threads and child processes natively, and is the only one maintained going forward. - Source map
- A separate
.mapfile (or inlinedata:URL) that maps compiled output back to original source — line, column, and identifier. Required for breakpoints in TypeScript, Babel-compiled JS, minified bundles, and CoffeeScript-era code. - Compound configuration
- An entry in the top-level
compoundsarray that bundles several named configurations into one Run-panel target. F5 starts all members;stopAll: truestops them together.
Frequently asked questions
What is launch.json in VS Code?
launch.json is a JSON file in the .vscode/ folder of a workspace that defines debug configurations for the VS Code Run and Debug panel. Each configuration tells VS Code which debug adapter to use (the "type" field — node, python, chrome, lldb, etc.), whether to launch a new process or attach to an existing one (the "request" field), which program or URL to target, and what arguments, environment variables, and source-map settings to apply. The file has two top-level keys: a schema "version" set to "0.2.0", and a "configurations" array. Pressing F5 runs the currently-selected configuration; the dropdown at the top of the Run panel lets you switch between them. VS Code reads launch.json fresh on every debug session, so edits take effect on the next F5 without a reload.
What's the difference between "launch" and "attach"?
The "request" field has two valid values: "launch" and "attach". "launch" starts a fresh process under the debugger — VS Code spawns the program, injects the debug runtime, and stops at breakpoints or the entry point. You control the command line via "program", "runtimeArgs", "args", "cwd", and "env". "attach" connects the debugger to a process that is already running with debug ports open — for Node this means the process started with --inspect or --inspect-brk; for Chrome it means Chrome was launched with --remote-debugging-port. attach is the only choice for remote debugging, containerized apps, or any process you did not start yourself. The same configuration object format works for both, but attach configs typically need "port", "address", or "processId" instead of "program".
How do I debug a TypeScript file without compiling first?
Use tsx or ts-node as the runtime executable. Set "type": "pwa-node", "runtimeExecutable": "tsx" (after installing tsx), and "program": "${file}" or a fixed entry path. VS Code will run the TypeScript file directly and resolve breakpoints against the original source via the runtime's built-in source maps. Example: {"type": "pwa-node", "request": "launch", "name": "Debug current TS file", "runtimeExecutable": "tsx", "args": ["${file}"], "skipFiles": ["<node_internals>/**"]}. If you prefer ts-node, set "runtimeArgs": ["--loader", "ts-node/esm"] and "program": "${file}". For Vitest or Jest TS tests, you usually do not need a custom launch config — the JavaScript Debug Terminal auto-attaches and source maps work out of the box.
Why does my breakpoint say "Unbound breakpoint"?
An unbound (grey, hollow) breakpoint means VS Code set the breakpoint but the debug adapter has not matched it to a loaded source file. Common causes: (1) the file has not been imported yet — set the breakpoint earlier in the entry path or run further; (2) source maps are missing or wrong — verify the compiled output has //# sourceMappingURL=... and that paths inside the .map file resolve from the runtime's cwd; (3) "outFiles" in launch.json does not include the directory containing the compiled JS — add it as a glob like "${workspaceFolder}/dist/**/*.js"; (4) the file is in node_modules and "skipFiles" matches it; (5) for Chrome configs, "webRoot" does not point to the source root the browser actually serves. Toggling "sourceMaps": true and increasing "trace": true logs every resolution attempt to the Debug Console.
How do I pass environment variables in launch.json?
Use the "env" field — an object of name/value string pairs merged into the launched process's environment. Example: "env": {"NODE_ENV": "development", "API_KEY": "abc123", "DEBUG": "myapp:*"}. To load variables from a .env file instead, set "envFile": "${workspaceFolder}/.env" — VS Code parses KEY=value lines and ignores comments. "env" entries override "envFile" entries when both are set. To reference existing host environment variables, use the ${env:NAME} substitution inside the value: "env": {"FULL_URL": "${env:HOST}/api"}. Setting a value to null removes that variable from the environment. Note: "env" works on "launch" requests only — for "attach" the process is already running, so its environment was fixed at start time.
Can launch.json have comments?
Yes. VS Code parses launch.json as JSONC (JSON with Comments), so // single-line and /* multi-line */ comments are both legal, and so are trailing commas. This is true for every JSON file VS Code owns — settings.json, tasks.json, launch.json, keybindings.json, and the user/workspace settings — even though strict JSON (RFC 8259) forbids comments. The schema you get from auto-complete also annotates each field with hover-doc descriptions. If a tool outside VS Code reads your launch.json (rare, but some CI helpers do), strip comments first with a JSONC-aware parser. See our JSON Comments guide for the full set of formats that allow comments.
How do I debug a process that runs in Docker?
Use an "attach" configuration that connects to the debug port the container exposes. For Node.js, start the container process with --inspect=0.0.0.0:9229 and publish port 9229 (-p 9229:9229). In launch.json, set "type": "pwa-node", "request": "attach", "port": 9229, "address": "localhost", "restart": true, and "localRoot"/"remoteRoot" to map host paths to container paths (e.g. "localRoot": "${workspaceFolder}", "remoteRoot": "/app"). The path mapping is what lets VS Code resolve breakpoints across the file-system boundary. For Python, use debugpy inside the container (python -m debugpy --listen 0.0.0.0:5678 --wait-for-client your_script.py) and an attach config with "connect": {"host": "localhost", "port": 5678} plus "pathMappings". The Dev Containers extension wraps this whole flow if you prefer not to manage the config by hand.
What is the "pwa-node" type and why does VS Code keep changing my type to it?
pwa-node is the type for the JavaScript Debugger that shipped as built-in for VS Code starting in 1.46. It replaced the older "node" type (the legacy debugger that used the V8 Inspector protocol via node-debug2). pwa-node is faster, supports worker threads and child processes natively, handles source maps better, and is the only one maintained going forward. VS Code auto-rewrites old "node" entries to "pwa-node" on save because the legacy adapter is deprecated and will eventually be removed. You can disable the rewrite via the setting "debug.javascript.usePreview": false but there is no good reason to — pwa-node is a strict superset of what "node" did. Same applies to pwa-chrome and pwa-msedge for browser targets, though those names are also accepted as just "chrome" and "msedge" in current versions.
Further reading and primary sources
- VS Code — Debugging — Official guide to the Run and Debug panel, launch.json schema, and debug features
- VS Code — Variables Reference — Full list of ${workspaceFolder}, ${file}, ${env:...}, and ${input:...} substitutions
- VS Code — Node.js Debugging — pwa-node configuration reference: runtimeArgs, skipFiles, outFiles, auto-attach
- Python in VS Code — Debugging — debugpy launch configurations: module vs program, justMyCode, Django, Flask, pytest
- Chrome DevTools Protocol — The underlying protocol Chrome and Edge debuggers speak; helpful when debugging the debugger