Filesystem — OPFS, memfs, node-fs, edge
Where do files live in cpp.js? Depends on the runtime and your
initCppJsoptions. This doc maps every combination.
The decision tree
Are you on the browser?
├── No (Node.js)
│ └── m.FS reads/writes the host filesystem via fs-node adapter.
│ No /opfs vs /memfs distinction; paths are real filesystem paths.
│
├── No (Cloudflare Worker / Deno Deploy / Vercel Edge)
│ └── m.FS is in-memory only.
│ No persistence across invocations.
│ No useWorker available (edge runtimes don't have Web Workers).
│ No OPFS available (browser-only API).
│
└── Yes (browser)
│
├── Need persistence across page reloads?
│ ├── No → fs: { opfs: false } → mount under /memfs/<app>/
│ │
│ └── Yes → REQUIRES useWorker: true → mount under /opfs/<app>/
│ ├── Browser supports OPFS? (Chrome 86+, FF 111+, Safari 15.2+)
│ │ ├── Yes → real persistence
│ │ └── No → cpp.js logs error + redirects to /memfs/<app>/
│ │
│ └── Mounted from main thread (no useWorker)?
│ → throws: "OPFS is only available inside a Worker scope"
│
└── Need to mount user-provided files?
→ Use m.autoMountFiles(files, parentPath?) with File[] from <input type=file>
The two virtual roots in browser
cpp.js mounts two namespaces under the Wasm filesystem root:
| Mount | Backed by | Persistence | Available when |
|---|---|---|---|
/opfs/<app>/ | Browser's Origin Private File System | Survives page reloads, browser restarts | useWorker: true + fs.opfs !== false + browser support |
/memfs/<app>/ | In-memory (Emscripten MEMFS) | Tab session only | Always |
<app> is general.name from cppjs.config.js (the same name the CLI uses for lib<name>.a).
Helpers on the Module object
m.getDefaultPath() // → '/opfs' or '/memfs' depending on config
m.getFinalPath(path) // validate + auto-fallback if OPFS unavailable
m.getRandomPath(startPath?) // → '<startPath>/<app>/automounted/<random>'
// (creates the dir tree)
m.autoMountFiles(files, parentPath?)
// files: File[] (e.g. from <input type=file>)
// parentPath: optional; if omitted, uses getRandomPath()
// Streams each file into the Wasm fs, returns the mount paths.
// Returns: Promise<string[]> (the resolved paths of each mounted file)
m.getFileBytes(path) // → Uint8Array
m.getFileList(startPath?) // → [{ path, size }, ...] (recursive)
Writing your own files
Use m.FS directly (Emscripten's standard API):
m.FS.mkdirTree('/memfs/myapp/cache')
m.FS.writeFile('/memfs/myapp/cache/data.bin', new Uint8Array([1, 2, 3]))
const bytes = m.FS.readFile('/memfs/myapp/cache/data.bin')
If you write to /opfs/... without useWorker: true, this throws. If OPFS isn't supported by the browser, the path is auto-redirected to /memfs/... (with a console.error).
Mounting from a <input type=file> element
const fileInput = document.querySelector('input[type=file]')
fileInput.addEventListener('change', async () => {
const paths = await m.autoMountFiles(Array.from(fileInput.files))
// `paths` is e.g. ['/opfs/myapp/automounted/123456/photo.jpg', …]
for (const p of paths) {
m.processImage(p) // call into your C++
}
})
To put them in a known location instead of an auto-random dir:
await m.autoMountFiles(files, '/opfs/myapp/uploads')
Reading from m.FS and shipping bytes back to JS
m.processImage('/opfs/myapp/uploads/photo.jpg') // C++ writes /opfs/myapp/uploads/photo.processed.jpg
const bytes = m.getFileBytes('/opfs/myapp/uploads/photo.processed.jpg')
const blob = new Blob([bytes], { type: 'image/jpeg' })
const url = URL.createObjectURL(blob)
imgEl.src = url
Common pitfalls
- Mounting
/opfsfrom main thread withoutuseWorker: true→ throws synchronously insidem.getFinalPath(). The error message tells you to enableuseWorkeror mount under/memfs/instead. - Forgetting the
<app>prefix → cpp.js auto-creates/memfs/<app>/automountedat startup. If you write to/memfs/foo(no<app>) it works but won't be cleaned up onterminate. - Assuming OPFS persists across origins — it doesn't. Files written from
app.example.comare invisible toother.example.com. Standard origin isolation. - Using
m.FSfrom beforeonRuntimeInitialized—m.FSis undefined until init completes. Use theonRuntimeInitialized: (m) => {…}hook or await theinitCppJs(...)promise first. - Calling
fs:{ opfs: false }and then mounting/opfs/...→ throws: "OPFS is disabled. Enable fs.opfs in config to mount under /opfs/."
Per-runtime cheat sheet
| Runtime | useWorker | /opfs/... | /memfs/... | Notes |
|---|---|---|---|---|
| Browser (no worker) | n/a | ❌ throws | ✅ | Tab-session memory only |
Browser + useWorker: true | ✅ | ✅ if browser supports OPFS, else fallback to /memfs/ | ✅ | Persistent option |
| Node.js | n/a | n/a | n/a (uses host fs) | m.FS reads real disk via fs-node adapter |
| Cloudflare Worker / edge | ❌ unavailable | ❌ unavailable | ✅ in-memory equivalent | No persistence; per-invocation memory |
| React Native (CLI / Expo) | n/a (uses JSI bridge) | n/a | n/a | App's sandbox storage via React Native APIs |
See also
init.md—useWorker,fs.opfsoptions.threading.md—useWorkeris independent of threading.- Source:
cppjs-core/cpp.js/src/assets/js-runtime/adapters/fs-browser.js(the OPFS guards).