An in-browser C++20 IDE delivered as a Chrome / Chromium extension.
| Feature | Detail |
|---|---|
| Editor | Monaco Editor (the engine behind VS Code) |
| Compiler | WASM-native Clang (runs entirely in the browser, offline) |
| Terminal | xterm.js with a bash-like shell and live line input on Chromium and Firefox 153+ |
| File access | File System Access API on Chromium, fallback open/save/folder flows on Firefox |
| File I/O | fstream / ifstream / ofstream – read and write workspace files at runtime |
| Standards | C++14 · C++17 · C++20 (selectable in the toolbar) |
npm installThe compiler binary is not shipped in this repository (~43 MB). Download the pre-built browsercc binary (LLVM 20, C++23-capable):
npm run fetch-clangBuilding your own binary (custom LLVM version): See § Building Clang WASM from source below.
npm run lint # lint the repository
npm run build # production build → dist/
# or
npm run dev # development build with watch mode- Open chrome://extensions, edge://extensions, brave://extensions, or chromium://extensions
- Enable Developer mode (top-right toggle)
- Click Load unpacked and select the
dist/folder - Click the browser.cpp icon in the toolbar (or press the extension button) to open the IDE in a new tab
browser.cpp/
├── manifest.json Chrome extension manifest (MV3)
├── package.json NPM scripts & dependencies
├── webpack.config.js Build configuration
│
├── src/
│ ├── background/
│ │ ├── background-main.js Shared background logic
│ │ ├── firefox-background.js Firefox background script entry
│ │ └── service-worker.js Chromium MV3 service worker entry
│ ├── ui/
│ │ ├── index.html IDE shell
│ │ ├── styles.css VS Code–inspired dark theme
│ │ ├── app.js Entry point – boots all subsystems
│ │ ├── editor.js Monaco editor setup & diagnostic API
│ │ ├── terminal.js xterm.js terminal + shell emulator
│ │ ├── filesystem.js File System Access API wrapper
│ │ └── toolbar.js Toolbar buttons & keyboard shortcuts
│ └── workers/
│ └── compiler.worker.js WASM Clang loader, compile, WASI run
│
├── scripts/
│ ├── generate-icons.js Generates PNG extension icons (prebuild)
│ └── fetch-clang-wasm.js Downloads clang.js + clang.wasm
│
├── dist/ ← Chromium-family unpacked extension output
│ ├── manifest.json
│ ├── index.html
│ ├── bundle.js
│ ├── service-worker.js
│ ├── firefox-background.js
│ ├── compiler.worker.js
│ ├── editor.worker.js (emitted by monaco-editor-webpack-plugin)
│ ├── ts.worker.js (emitted by monaco-editor-webpack-plugin)
│ ├── icons/
│ └── clang/
│ ├── clang.js (downloaded by npm run fetch-clang)
│ └── clang.wasm (downloaded by npm run fetch-clang)
└── dist-firefox/ ← Firefox unpacked extension output
└── manifest.json
Workspace sources (+ unsaved tab overlay)
│ postMessage {type:'compile', sourcePaths, files, std, flags, outputName}
▼
compiler.worker.js ──importScripts──▶ dist/clang/clang.js
│ clang++ -### → multi-TU compile plan (one -cc1 per source + wasm-ld)
│ compile each source in a fresh Clang, link all objects with LLD
│◀── Emscripten Module ──────────────────┘
│
│ output.wasm (WASI binary) read from virtual FS
│
│ WebAssembly.instantiate(output.wasm, { wasi_snapshot_preview1: … })
│
▼
WASI shim (built into compiler.worker.js)
│ stdout/stderr streamed back via postMessage
▼
terminal.js → xterm.js display
When a folder is opened with Ctrl+O (or Open folder), the compiled program can read and write files in that folder using standard C++ file streams:
#include <fstream>
#include <string>
int main() {
// Read a file
std::ifstream in("input.txt");
std::string line;
while (std::getline(in, line)) { /* … */ }
// Write a file
std::ofstream out("output.txt");
out << "Hello from browser.cpp!\n";
}How it works: Before each run the extension reads all workspace files into
an in-memory virtual filesystem (VFS) that is exposed to the WASM program via
the WASI snapshot_preview1 file-system API. After the program exits, any
files the program created or modified are written back to the real folder on
disk. Opening a folder requests read/write permission so that outputs can
be persisted.
When no folder is open, fstream opens will fail as expected
(failbit is set), and no files are written back.
browser.cpp supports C++14, C++17, and C++20 compilation plus common in-memory
libc++/STL use (containers, strings, algorithms, smart pointers, and streams).
stdout/stderr, random bytes, wall-clock time, supported live line-buffered
stdin, and workspace-backed fstream/ifstream/ofstream are available. See
fstream / File I/O for the workspace write-back model.
This is not an exhaustive STL compatibility matrix: availability ultimately depends on the bundled WASI libc/libc++ sysroot and browser.cpp's narrow runtime shim. OS-dependent features are intentionally limited:
- No networking or sockets; no subprocesses, shell execution,
system,fork, orexec. - No real environment variables and fixed program arguments (
./a.outonly). - No reliable
std::threador thread-backed concurrency support; this is not a raw POSIX terminal. - No full host-filesystem semantics beyond the opened workspace: do not rely on directory iteration, symlinks, permissions, or other platform-specific file operations. Locale databases and other platform-specific facilities are also not guaranteed.
| Action | Shortcut |
|---|---|
| Compile & Run | F5 |
| Compile only | Ctrl+Shift+B |
| Run (last build) | Ctrl+Shift+R |
| Save | Ctrl+S |
| Open folder | Ctrl+O |
| New | Ctrl+N |
| Clear terminal | Ctrl+K |
| Command | Description |
|---|---|
g++ [files…] [flags] [-std=c++NN] [-o out] |
Compile explicit source files (e.g. g++ main.cpp other.cpp). With no files given, compiles the current editor buffer. .c/.cc inputs are rejected in this MVP. |
./a.out / ./<name> |
Run the last compiled binary (use ./<name> after -o <name>) |
clear |
Clear the terminal |
echo <text> |
Print text |
ls [-R] [dir] |
List files/folders from the opened workspace folder |
cd [dir] |
Change the current workspace directory |
mkdir [-p] <dir> |
Create workspace directories (-p creates missing parents) |
touch <file> |
Create an empty file in the opened workspace (existing files are not overwritten) |
cat <file> |
Print file contents |
pwd |
Print working directory |
help |
Show command list |
Folders opened in browser.cpp may still be git repositories, but the simulated
terminal does not support git commands; for example, git status returns
bash: git: command not found. Treat .git files and folders as ordinary
workspace content, and use a real local terminal for version control operations.
The toolbar Compile / Compile & Run buttons build the whole opened
workspace: every recursive .cpp and .cxx file is compiled and linked
together (.c/.cc are ignored for this MVP). When no folder is open, they fall
back to compiling the single editor buffer.
Builds reflect the live, in-memory project: unsaved edits in open tabs are
overlaid on top of the on-disk files before compiling, so local includes such as
#include "other.cpp" or #include "app.hpp" resolve against the opened folder
even when the referenced file has unsaved changes. Compiler/linker diagnostics
for all files print in the terminal, while inline editor markers stay scoped to
the active file. Compile & Run runs the actual built artifact (honouring a
-o name), not a hardcoded a.out.
Full feature parity is supported for desktop Chrome, Edge, Brave, and Chromium when the browser is based on Chromium 105 or newer. Latest stable is recommended for release testing.
Firefox desktop can load the extension, but support remains experimental:
- compile/run, Monaco, and extension-runtime flows are supported
- Firefox 153+ uses WebAssembly JSPI for live, line-buffered
std::cin,std::getline, andscanfinput; Firefox runtimes without worker-side JSPI cannot run stdin-reading programs - file open/save and folder import use fallback browser flows rather than Chromium File System Access APIs
- persistent folder write-back and directory-handle session restore may be reduced compared with Chromium-family builds
- public AMO publication is manual; the protected release workflow generates the Mozilla-signed unlisted XPI for self-distribution
Full parity requires:
- Manifest V3 extension APIs (
chrome.runtime,chrome.tabs,chrome.storage) - File System Access APIs (
showOpenFilePicker,showDirectoryPicker,showSaveFilePicker) - Web Workers and WebAssembly
SharedArrayBufferandAtomics.waitAsyncfor Chromium live interactive stdinWebAssembly.SuspendingandWebAssembly.promisingfor Firefox 153+ live stdin- Managed browser policies that allow local file read/write prompts
Run the fast checks before browser-specific smoke tests:
npm run release:clean
npm run fetch-clang
npm run lint
npm run build
npm run test:e2e
npm run test:preflight-clang
npm run version:check
npm run release:check-versiontest:preflight-clang requires these files to exist under dist/clang/:
clang.js, clang.wasm, lld.js, lld.wasm, and sysroot.tar. Run
npm run fetch-clang before browser smoke tests or release packaging.
Run smoke tests for each Chromium-family target:
npm run test:browser:chrome
npm run test:browser:edge
npm run test:browser:brave
npm run test:browser:chromiumRun the Firefox packaging smoke separately:
npm run test:browser:firefoxFor release candidates triggered by a manifest.json version bump, GitHub Actions
uploads the shared Chromium-family release artifact for same-repo pull requests only. The
release workflow uses manifest.json as the version source of truth and expects
package.json to stay in sync with that value. See docs/release-playbook.md
for the full release flow and the remaining human-owned publish steps.
The smoke runner auto-discovers common browser install paths. Override discovery
with CHROME_PATH, EDGE_PATH, BRAVE_PATH, CHROMIUM_PATH, or a generic
BROWSER_PATH.
If auto-discovery misses a browser, set the path explicitly before running a single target:
CHROME_PATH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" npm run test:browser:chrome
EDGE_PATH="/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" npm run test:browser:edge
BRAVE_PATH="/Applications/Brave Browser.app/Contents/MacOS/Brave Browser" npm run test:browser:brave
CHROMIUM_PATH="/Applications/Chromium.app/Contents/MacOS/Chromium" npm run test:browser:chromiumCommon binary locations:
- macOS:
- Chrome:
/Applications/Google Chrome.app/Contents/MacOS/Google Chrome - Edge:
/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge - Brave:
/Applications/Brave Browser.app/Contents/MacOS/Brave Browser - Chromium:
/Applications/Chromium.app/Contents/MacOS/Chromium
- Chrome:
- Linux:
- Chrome:
/usr/bin/google-chromeor/usr/bin/google-chrome-stable - Edge:
/usr/bin/microsoft-edgeor/usr/bin/microsoft-edge-stable - Brave:
/usr/bin/brave-browseror/usr/bin/brave - Chromium:
/usr/bin/chromiumor/usr/bin/chromium-browser
- Chrome:
- Windows:
- Chrome:
%LOCALAPPDATA%\\Google\\Chrome\\Application\\chrome.exe - Edge:
%LOCALAPPDATA%\\Microsoft\\Edge\\Application\\msedge.exe - Brave:
%LOCALAPPDATA%\\BraveSoftware\\Brave-Browser\\Application\\brave.exe - Chromium:
%LOCALAPPDATA%\\Chromium\\Application\\chrome.exe
- Chrome:
For CI, set the same variables on the job before invoking the smoke script, for example:
export CHROME_PATH=/path/to/google-chrome
export EDGE_PATH=/path/to/msedge
export BRAVE_PATH=/path/to/brave-browser
export CHROMIUM_PATH=/path/to/chromium
npm run test:browserAutomated smoke tests validate extension load, required browser APIs, compiler
asset loading, Monaco rendering, default C++ compile-and-run, and console-error
absence. Native showDirectoryPicker() path selection is not exposed through a
stable Chromium DevTools automation API, so each release also needs this manual
check in every target browser:
- Open a local folder.
- Create a new source file.
- Save and Save As.
- Compile a multi-file project.
- Run a program that reads stdin.
- Write an output file with
std::ofstream. - Close and reopen the browser, then restore the previous workspace.
Create the shared Chromium-family release ZIP from the built dist/ contents:
npm run package:releaseThis writes:
release/browser-cpp-chromium-family-v<version>.zipfor Chrome, Edge, Brave, and Chromiumrelease/firefox-unlisted/*.xpiafter the protected release workflow signs the Firefox unlisted buildrelease/SHA256SUMS-v<version>.txtrelease/release-manifest-v<version>.json
The release manifest tracks the browser package matrix:
- Chrome, Edge, Brave, and Chromium map to the same Chromium-family ZIP
- Firefox has its own manifest, background entry, smoke-tested temporary package, and signing metadata
Chrome, Edge, Brave, and Chromium still share the same MV3 payload. Firefox is
built from dist-firefox/ as a separate payload because its manifest and
background model differ from Chromium; release distribution uses the signed XPI.
Store submission notes should state:
- Minimum browser version: Chromium 105+, latest stable recommended
- Local file prompts are required for folder read/write
- Compiler assets are packaged with the extension
- Programs execute inside the extension's WASI/WebAssembly sandbox
- No remote code execution is used
Use .github/workflows/release.yml to publish one GitHub Release per
manifest.json version. On pushes to main, the workflow:
- Reads
manifest.json.version - Skips work if GitHub Release
v<version>already exists - Verifies manifest-driven version sync
- Cleans
dist/andrelease/ - Fetches the Clang toolchain
- Runs lint, build, release validation, and E2E checks
- Runs Firefox packaging smoke validation
- Produces one Chromium-family ZIP plus checksums and release metadata
- Signs the Firefox unlisted XPI with protected AMO credentials
- Creates or updates GitHub Release
v<version>and uploads all files underrelease/
Use workflow_dispatch with force=true to rebuild and re-upload assets for an
existing release. The workflow does not publish directly to browser stores.
Store publication and Chromium distribution remain human-owned steps.
Public AMO publication also remains human-owned even though the unlisted Firefox
XPI is signed automatically during release.
These steps happen after the automated release workflow or local release commands
have produced a validated release/ directory and, for GitHub-distributed
assets, published GitHub Release v<version>.
- Verify you still have access to the existing Chrome Web Store item for browser.cpp.
- Upload
release/browser-cpp-chromium-family-v<version>.zip. - Update listing copy, screenshots, privacy details, and reviewer notes if the release changes user-visible behavior, permissions, or file-access guidance.
- Confirm the listing still describes the extension as a local-only WASI/WebAssembly compiler with user-approved file access prompts.
- Submit the draft, monitor review, and address any reviewer questions.
- After publication, install/update from the public listing and verify the release in Chrome.
- Verify the Microsoft Partner Center account is active, or create it before the first Edge release.
- Create the Edge Add-ons listing if one does not exist yet.
- Upload
release/browser-cpp-chromium-family-v<version>.zip. - Complete the store listing fields, availability/market settings, privacy links, and any certification notes requested by Partner Center.
- Submit for certification and respond to reviewer feedback.
- After publication, install/update from the Edge Add-ons listing and verify the release in Edge.
- Run
npm run test:browser:brave. - Load the validated release in Brave and complete the manual QA checklist below.
- Install the published Chrome Web Store listing in Brave and verify the end-user install/update flow.
- If Brave-specific notes are needed for users or reviewers, add them to the project documentation before announcing the release.
Brave does not use a separate store submission flow here; it rides on Chrome Web Store compatibility plus Brave-specific validation.
- Verify that GitHub Release
v<version>includes:release/browser-cpp-chromium-family-v<version>.ziprelease/SHA256SUMS-v<version>.txtrelease/release-manifest-v<version>.json
- Publish manual installation instructions for Chromium users, including that the extension is loaded outside a browser store.
- Verify the packaged artifact can be loaded in Chromium and passes the manual QA checklist below.
There is no official Chromium extension store in this workflow; Chromium is a manual/GitHub-distributed channel.
- Run
npm run test:browser:firefox. - Review
amo/metadata/listed.jsonand update it if the release changes Firefox-facing product behavior or listing copy. - For public AMO publication, build the Firefox package from
dist-firefox/, then upload it with the metadata manually through the owner-managed listing workflow. - For self-distribution, verify that the protected release workflow produced a
signed artifact under
release/firefox-unlisted/. - Install the signed XPI in Firefox and complete the manual QA checklist
in
docs/firefox-stdin-runtime-acceptance.md, paying special attention to JSPI live stdin and the documented workspace-persistence limitations.
Perform these checks in Chrome, Edge, Brave, and Chromium before publishing:
- Open a local folder.
- Create a new source file.
- Save and Save As.
- Compile a multi-file project.
- Run a program that reads stdin.
- Write an output file with
std::ofstream. - Close and reopen the browser, then restore the previous workspace.
For a custom LLVM version or offline builds, compile Clang with Emscripten. The compiler worker accepts either output format:
ES6 module format (recommended, Emscripten 3.0+):
# Prerequisites: Emscripten SDK (emsdk), CMake, Ninja
git clone https://github.com/llvm/llvm-project
cd llvm-project
emcmake cmake -S llvm -B build-wasm -G Ninja \
-DLLVM_ENABLE_PROJECTS="clang" \
-DLLVM_TARGETS_TO_BUILD="WebAssembly" \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DLLVM_BUILD_TOOLS=OFF \
-DLLVM_INCLUDE_TESTS=OFF \
-DEMSCRIPTEN_EXTRA_LINK_FLAGS="-s MODULARIZE=1 -s EXPORT_ES6=1 -s EXPORTED_RUNTIME_METHODS=[FS,callMain]"
cmake --build build-wasm --target clang -j$(nproc)Classic format (legacy Emscripten):
emcmake cmake -S llvm -B build-wasm -G Ninja \
...same flags... \
-DEMSCRIPTEN_EXTRA_LINK_FLAGS="-s MODULARIZE=1 -s EXPORT_NAME=createClangModule -s EXPORTED_RUNTIME_METHODS=[FS,callMain]"Copy the resulting clang.js and clang.wasm into dist/clang/.
The binary will be large (~40–120 MB for clang.wasm). You can also host it on a CDN and update the
BASE_URLinscripts/fetch-clang-wasm.js.
- Binary size: The Clang WASM binary is large; first load may take a few seconds. Subsequent loads use the browser cache.
- No network access: Programs run in a sandboxed WASI environment with no socket support.
- Standard library: Only the subset of libc/libc++ compiled into the WASM sysroot is available.
- C++ exceptions:
try,catch, andthroware not supported. The bundled WASI C++ runtime has no exception-unwinding support, so use return values, error-state checks (such asstream.fail()), or other non-throwing error handling instead. - Execution time: Long-running programs may trigger the browser's "unresponsive script" dialog. The compiler runs in a dedicated Web Worker to avoid blocking the UI.
- Browser scope: Full parity targets desktop Chrome, Edge, Brave, and Chromium. Firefox 153+ supports live canonical (line-buffered) stdin through JSPI; older or no-JSPI Firefox runtimes cannot provide terminal stdin. This is not a raw POSIX PTY, and Firefox workspace-persistence limitations remain. Safari is outside the current release target.
- Managed browsers: Enterprise policies that block File System Access prompts prevent full local workspace read/write support.
MIT