BizTechLab

IDEASINNOVATIONIMPACT

Web APIs

OPFS & WASM-SQLite

The browser's native file-system access API — and how it makes a real SQLite database possible entirely client-side.

2 August 20267 min read

Overview

The (Origin Private File System) is a browser API that gives JavaScript — specifically, synchronous access from inside a Web Worker — direct, high-performance access to a private, sandboxed filesystem. It's the piece of infrastructure that makes it practical to run , a full copy of SQLite compiled to WebAssembly, entirely inside the browser with real file-backed persistence, instead of a database that lives only in memory.

Why It Exists

IndexedDB is genuinely useful, but its API is awkward for anything that expects a real file interface, and it isn't fast enough for the kind of heavy, random-access reads and writes a database engine performs constantly. OPFS exists to give web apps an actual file system primitive — synchronous read/write handles, fast enough that porting an existing native database engine to the browser stops being a novelty and starts being genuinely usable in production.

Real World Example

This is the exact technology behind the SQL Playground built earlier in this journey. That playground keeps its database in memory for simplicity — reload the page, and it's gone. Point that same WASM-SQLite engine at OPFS instead, and the database file persists to disk inside the browser across reloads and even browser restarts — which is how real local-first apps (offline note-taking tools, browser-based dev utilities) ship a full relational database with zero backend server at all.

How the Pieces Fit Together

OPFS — a Private Filesystem per Origin

getDirectory()getFileHandle()

Every origin gets its own sandboxed directory tree that JavaScript can read and write like real files — but it's invisible through the OS's normal file explorer, and exists purely for that origin's own use.

const root = await navigator.storage.getDirectory();
const fileHandle = await root.getFileHandle('app.db', { create: true });

Synchronous Access Requires a Web Worker

createSyncAccessHandle()

The fast, synchronous read/write handle OPFS offers is only available inside a dedicated Web Worker, never the main thread — a deliberate restriction so that disk I/O can never block page rendering.

// inside a Worker
const accessHandle = await fileHandle.createSyncAccessHandle();
accessHandle.write(data, { at: 0 });

WASM-SQLite — the Real Engine, Compiled to Run in the Browser

SQLite's actual C source code, compiled to WebAssembly — the same battle-tested engine used in countless native mobile and desktop apps, running with near-native speed inside a browser tab.

Persisting to OPFS Instead of Memory

A WASM-SQLite build can be configured to use OPFS as its storage backend (its 'VFS', or virtual file system), so every write actually lands on disk through the mechanism above — the difference between a database that survives a page reload and one that doesn't.

Diagram

The main thread never touches the disk directly — the worker does

Main thread

renders the page, posts messages to the worker

Web Worker

runs WASM-SQLite

OPFS sync access handle

fast, synchronous file I/O

Data persists on disk

survives reloads and browser restarts

Syntax

// Inside a dedicated Web Worker
const root = await navigator.storage.getDirectory();
const fileHandle = await root.getFileHandle("app.db", { create: true });
const accessHandle = await fileHandle.createSyncAccessHandle();

accessHandle.write(new TextEncoder().encode("hello"), { at: 0 });
accessHandle.flush();
accessHandle.close();

// Requesting durability so the browser is less likely to evict this origin's storage
const granted = await navigator.storage.persist();

Common Mistakes

Calling createSyncAccessHandle() from the main thread

Why: It's only available inside a dedicated Web Worker by specification — calling it on the main thread throws, since synchronous file I/O there would risk freezing the page.

Fix: Move all direct OPFS file access into a Web Worker, and communicate with it from the main thread via postMessage.

Assuming OPFS files are visible to the user or synced anywhere

Why: OPFS storage is origin-private and local to that one browser profile — it's invisible in the regular file system, and nothing about it is automatically backed up or synced across devices.

Fix: Don't rely on OPFS for anything the user should be able to see or export without building an explicit export feature yourself.

Treating OPFS storage as guaranteed to persist forever

Why: Like IndexedDB, browsers can evict an origin's storage under disk pressure unless that origin has been granted persistent storage — clearing browser data wipes it entirely either way.

Fix: Call `navigator.storage.persist()` for apps that genuinely need durability, and always have a real recovery or sync path for data that matters.

Interview Questions

beginner

What is OPFS, in plain terms?

A browser API that gives a website its own private, sandboxed filesystem — invisible to the user through the normal OS file explorer, and used purely for that origin's own storage needs.

intermediate

Why does OPFS's fast, synchronous file access only work inside a Web Worker?

Synchronous disk I/O can take a noticeable amount of time, and running it on the main thread would block page rendering and user interaction. Restricting synchronous access handles to Web Workers keeps that I/O off the thread responsible for the page's UI.

senior

What would it take to make a WASM-SQLite-backed web app genuinely durable, and what are the honest limits of that?

You'd back the SQLite instance with OPFS for real file persistence, request persistent storage via `navigator.storage.persist()` to reduce the chance of eviction, and run the database inside a Web Worker for performance. The honest limit is that none of this is a substitute for a real backup: a wiped browser profile, a user switching devices, or a rare-but-real storage eviction still means total data loss unless the app also syncs that data somewhere else, like a server.

Production Best Practices

Do

Do all direct OPFS file access inside a dedicated Web Worker.

Request persistent storage for apps where losing local data would be a real problem.

Build an explicit export/sync path for any data the user should be able to recover elsewhere.

Don't

Don't call createSyncAccessHandle() from the main thread — it's worker-only.

Don't treat OPFS as visible, synced, or backed-up storage — it's none of those by default.

Don't assume persistent storage guarantees are permanent — a cleared browser profile still wipes it.

Comparison

IndexedDBOPFS + WASM-SQLite
Access patternKey/value + indexesFull relational SQL
Query capabilityObject store queries, cursorsReal SQL — joins, aggregates, subqueries
Runs on main thread?YesNo — requires a Web Worker for sync access
Best forSimple structured storageApps that need a genuine embedded database

Related Articles