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 (
-nomusicflag). 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
Escto 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.playcommand fires →wadManager.ensureWad()checks / downloads FreeDOOMDoomPanel.createOrShow()opens aWebviewPanelwith a canvas and a start-screen overlay- User clicks ▶ PLAY DOOM →
AudioContextis created (browser autoplay policy requires a user gesture) window.Moduleis configured with Emscripten options, thenwebsockets-doom.jsis injected via<script>- WASM compiles, FreeDOOM WAD is fetched into Emscripten's virtual filesystem
onRuntimeInitializedfires → globalcallMain(['-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.