DOOM for VS Code 🔫
Play classic DOOM directly inside Visual Studio Code using WebAssembly.
Powered by
Websockets Doom — a Chocolate Doom port compiled
to WASM via Emscripten. Uses FreeDOOM game data
(downloaded automatically on first run). Commercial WADs are also supported.
Search terms: doom, vscode doom, doom extension, chocolate doom, freedoom, wad, webassembly, retro fps.

Play!
Ctrl+Shift+P → DOOM: Play DOOM 🔫
FreeDOOM (~56 MB) is downloaded automatically on first run and cached locally.
Subsequent launches are instant.
Controls
| Key |
Action |
W / ↑ |
Move forward |
S / ↓ |
Move backward |
A / ← |
Turn left |
D / → |
Turn right |
Alt + A / D |
Strafe |
Shift |
Run |
Ctrl |
Fire |
Space / E |
Use / Open door |
Tab |
Automap |
Esc |
Menu |
1–7 |
Select weapon |
| Mouse |
Look / aim (click the canvas to capture) |
Settings
Open with Ctrl+Shift+P → DOOM: Configure WAD File, or edit your VS Code settings.json:
| Setting |
Default |
Description |
doom.wadPath |
"" |
Absolute path to a custom .wad file. Leave empty to use auto-downloaded FreeDOOM. |
doom.enableSound |
true |
Enable sound effects. Disable if you experience audio issues. Note: music is always off (see Known Limitations). |
Developer notes
Using a Commercial WAD
If you own Doom 1 or Doom 2 (available on Steam
or GOG):
// settings.json
"doom.wadPath": "C:/path/to/your/doom.wad"
Any IWAD compatible with Chocolate Doom works: doom.wad, doom2.wad, tnt.wad, plutonia.wad.
Known Limitations
- No music — MIDI / OPL music initialisation can hang in a browser WebAudio context
in this build. Music is disabled by default (
-nomusic flag). Sound effects work fine.
- No save games — Emscripten's virtual filesystem is in-memory. Saves are lost when
the panel is closed.
- Mouse capture — Click the canvas to capture the mouse for mouselook. Press
Esc
to release. VS Code may show a "pointer captured" indicator in the status bar.
- One panel at a time — Running the command when a DOOM panel is already open just
brings it to focus instead of starting a second instance.
Architecture
doom-extension/
├── src/
│ ├── extension.ts — activate(), registers doom.play + doom.configureWad commands
│ ├── DoomPanel.ts — WebviewPanel singleton; generates the HTML page with canvas
│ └── wadManager.ts — Downloads FreeDOOM WAD to globalStorageUri on first run
├── media/
│ ├── websockets-doom.js — Emscripten JS glue (Chocolate Doom, not included)
│ └── websockets-doom.wasm — Compiled Chocolate Doom WASM binary (not included)
└── scripts/
└── fetch-doom-assets.js — Helper script to download engine assets automatically
How it works
doom.play command fires → wadManager.ensureWad() checks / downloads FreeDOOM
DoomPanel.createOrShow() opens a WebviewPanel with a canvas and a start-screen overlay
- User clicks ▶ PLAY DOOM →
AudioContext is created (browser autoplay policy requires a user gesture)
window.Module is configured with Emscripten options, then websockets-doom.js is injected via <script>
- WASM compiles, FreeDOOM WAD is fetched into Emscripten's virtual filesystem
onRuntimeInitialized fires → global callMain(['-iwad', 'freedoom1.wad', '-window', '-nogui', '-nomusic', ...]) starts the engine
- Chocolate Doom renders into the
<canvas> via SDL2 → WebGL; audio via Web Audio API
Key VS Code WebView constraints solved
| Challenge |
Solution |
No SharedArrayBuffer in webview |
Websockets Doom uses a single-threaded Emscripten build — no COOP/COEP headers needed |
Emscripten uses eval() |
CSP includes 'unsafe-eval' in script-src |
| WASM file path wrong in sandboxed iframe |
Module.locateFile() remaps .wasm → webview.asWebviewUri() |
| 56 MB WAD would bloat the VSIX |
Downloaded to context.globalStorageUri on first run |
| AudioContext autoplay blocked |
AudioContext.resume() called inside the click handler of the Play button |
callMain is a global, not on Module |
Call bare callMain(args) — this build never assigns it to Module |
| Canvas context poisoning |
Never call canvas.getContext('2d') — SDL claims the canvas for WebGL; requesting 2D first locks it and prevents WebGL from initialising |
| Music hanging engine |
Always pass -nomusic; OPL/MIDI init stalls in browser WebAudio |
License
Extension code: MIT
FreeDOOM: FreeDOOM License (freely redistributable)
Chocolate Doom / Websockets Doom: GPL-2.0
This extension does not bundle the DOOM engine or any WAD files.
Engine assets (websockets-doom.js / .wasm) must be provided separately.
FreeDOOM game data is downloaded from the official FreeDOOM GitHub releases.