reaching real file system speed with sync access handles
The Origin Private File System is an isolated, high performance file store per origin that reaches direct file system speed through synchronous read and write handles inside web workers. It powers Wasm databases, image editors and offline tools that clearly outperform classic IndexedDB based solutions in throughput and latency.
Table of Contents
- 1. What the Origin Private File System really is
- 2. OPFS versus the File System Access API and IndexedDB
- 3. API basics: directories, files and handles
- 4. Sync access handles inside a dedicated worker
- 5. SQLite Wasm over OPFS as a virtual file system
- 6. Persistence, storage space and quota
- 7. Error handling, locking and browser support
- 8. Performance measurement head to head
- 9. Origin Private File System at a glance
- 10. Summary
- 11. FAQ
1. What the Origin Private File System really is
The Origin Private File System, OPFS for short, is part of the File System Access API and gives every origin its own isolated file system area that the user cannot reach directly through a file explorer. Unlike classic web storage mechanisms, the storage location is a real, browser managed file system with a directory structure, file handles and binary input and output. For applications that need to store large amounts of data in a structured way, such as image and video editors, offline databases or Wasm ports of native programs, the Origin Private File System is the first choice, because it offers file semantics instead of key value semantics.
The decisive difference from previous browser storage lies in the access pattern. While IndexedDB and the cache storage mechanism only offer asynchronous operations, the Origin Private File System provides so called sync access handles that can read and write synchronously inside a dedicated worker. This synchronicity is not accidental, it is specifically designed for ports of C and C plus plus code through WebAssembly, where synchronous file system behavior, similar to POSIX calls, is assumed. Anyone who previously had to build tedious asynchronous wrappers around IndexedDB just to get sqlite3 or similar libraries running in the browser will find the native solution to that problem in the Origin Private File System.
An important point for classification: the Origin Private File System is completely invisible to the end user. There is no dialog, no permission prompt and no path in the real file system of the operating system. The data lives in an internal, browser managed area that is deleted together with the website data. That makes the Origin Private File System a private but still structured storage location that sits between the anonymity of IndexedDB and the visibility of the File System Access API.
2. OPFS versus the File System Access API and IndexedDB
The Origin Private File System and the File System Access API are frequently confused, even though they serve different purposes. The File System Access API, with window.showOpenFilePicker() and window.showSaveFilePicker(), lets a web application access real files in the local file system with the user's consent, for example to open and save a document. The Origin Private File System, on the other hand, is a purely internal storage area invisible to the user that can be used without any permission dialog. Both APIs share the same FileSystemHandle interface, but their intended use differs fundamentally.
Compared to IndexedDB, the Origin Private File System excels above all with large, contiguous binary data. IndexedDB is optimized for structured objects with indexing, but incurs additional overhead for large blobs through serialization and transaction management. The Origin Private File System instead allows direct byte offsets, partial reads and writes of individual file regions and true streaming without keeping the entire file in memory. For use cases such as browser based video editing, large CSV exports or embedded databases, the Origin Private File System is clearly better suited than IndexedDB.
A practical decision criterion: anyone who needs to open and edit a user's local files needs the File System Access API. Anyone who wants to manage temporary or persistent working data purely inside the application, without the user ever facing a file dialog, is well served by the Origin Private File System. Many applications combine both APIs: a file is imported through the File System Access API and then copied into the Origin Private File System for fast intermediate processing.
// Root directory handle of the Origin Private File System
const opfsRoot = await navigator.storage.getDirectory();
// Create (or open) a nested directory structure
const projectsDir = await opfsRoot.getDirectoryHandle("projects", { create: true });
const draftsDir = await projectsDir.getDirectoryHandle("drafts", { create: true });
// Create a file handle inside that directory
const fileHandle = await draftsDir.getFileHandle("notes.json", { create: true });
// Read current contents asynchronously (works on main thread too)
const file = await fileHandle.getFile();
const text = await file.text();
console.log("Current contents:", text);
3. API basics: directories, files and handles
The entry point into the Origin Private File System is navigator.storage.getDirectory(), which returns a FileSystemDirectoryHandle for the origin's root directory. From there, getDirectoryHandle(name, { create: true }) creates arbitrarily deep directory trees, and getFileHandle(name, { create: true }) creates or opens files. This structure behaves like a normal hierarchical file system, including iteration over directory contents with the asynchronous iterator for await (const [name, handle] of directoryHandle.entries()).
For simple, infrequent write operations on the main thread, the Origin Private File System offers createWritable(), which returns a FileSystemWritableFileStream. This stream behaves asynchronously and resembles handling classic streams: write(), seek() and finally close(), which persists the changes. This variant is convenient for occasional writes, such as saving a configuration object, but for the high frequency writes that a database or editor requires it is too slow because of the asynchronous overhead and internal copy operations.
Deleting files and directories happens through removeEntry(name, { recursive: true }) on the parent directory handle. Since the Origin Private File System has no recycle bin, a deleted directory is gone irrevocably, unless the application has previously made its own backup copy. For production applications a simple versioning scheme is therefore recommended, for example creating a .bak copy before destructive operations in the Origin Private File System.
4. Sync access handles inside a dedicated worker
The real distinguishing feature of the Origin Private File System is the method createSyncAccessHandle(), which is only available inside a dedicated worker. It returns a FileSystemSyncAccessHandle that provides synchronous variants of read(), write(), truncate(), flush() and getSize(). These synchronous calls do block the worker thread, but that is exactly the point: they deliver deterministic, predictable latency without an event loop round trip, which is essential for Wasm code with a synchronous I/O model.
An important aspect of the Origin Private File System and sync access handles is the exclusive locking behavior: as long as a sync access handle is open for a file, no other context, whether in the same or a different worker, can open the same file at the same time. This automatically prevents race conditions at the file level, but it requires a deliberate architecture: write intensive operations should be bundled inside a dedicated worker that acts as the sole owner of the file, while the main thread communicates with that worker through postMessage().
// worker.js — runs inside a dedicated worker
self.onmessage = async (event) => {
const { type, payload } = event.data;
const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle("db.bin", { create: true });
// Sync Access Handle: only available inside a dedicated worker
const accessHandle = await fileHandle.createSyncAccessHandle();
if (type === "write") {
const encoder = new TextEncoder();
const buffer = encoder.encode(JSON.stringify(payload));
accessHandle.truncate(buffer.byteLength);
accessHandle.write(buffer, { at: 0 });
accessHandle.flush(); // force data to durable storage
accessHandle.close();
self.postMessage({ status: "written", bytes: buffer.byteLength });
}
if (type === "read") {
const size = accessHandle.getSize();
const buffer = new Uint8Array(size);
accessHandle.read(buffer, { at: 0 });
accessHandle.close();
const decoder = new TextDecoder();
self.postMessage({ status: "read", data: decoder.decode(buffer) });
}
};
From the main thread, the worker is instantiated normally and controlled through messages. Since createSyncAccessHandle() is itself asynchronous, because the Origin Private File System still needs to check locking logic internally, the first layer of communication remains asynchronous, while the actual read and write operations afterward run fully synchronously and without intermediate copies. This exact combination explains why the Origin Private File System is significantly faster than any IndexedDB based solution under heavy I/O load.
5. SQLite Wasm over OPFS as a virtual file system
The most prominent practical example of the Origin Private File System is the official SQLite Wasm distribution, which ships its own VFS layer, virtual file system, that uses sync access handles. This allows a complete, unmodified SQLite engine to run in the browser, with real transactions, SQL queries and ACID guarantees, while the database file physically lives in the Origin Private File System. For applications that need complex relational queries offline, such as a bookkeeping tool or an analytics dashboard, that is a much more powerful alternative to IndexedDB with its simple object store model.
Setting it up requires that the entire SQLite access runs inside a worker, because sync access handles are only available there. The official library already wraps this: sqlite3-worker1-bundler-friendly.mjs exposes a promise based message interface, so the main thread makes ordinary await calls while, in the background, the Origin Private File System handles the actual persistence. For teams that already bring SQL knowledge, this removes the need to recreate IndexedDB queries in an unfamiliar API.
A point that many teams underestimate: the OPFS VFS variant of SQLite Wasm only reaches full performance when crossOriginIsolated is active, meaning the response headers Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp are set. Without this isolation, SQLite Wasm falls back to a slower, non persistent VFS. Anyone who wants to use the Origin Private File System for SQLite in production has to configure these headers server side, otherwise the promised performance never materializes.
6. Persistence, storage space and quota
Data in the Origin Private File System is subject to the same quota rules as other origin storage: the available space depends on free disk space and a share that the browser allocates to the origin. Unlike classic cookies or local storage with fixed kilobyte limits, the Origin Private File System can, depending on available disk space, reach gigabyte sizes, which is what makes use cases like local video editing or large offline datasets practical in the first place.
The relationship with the persistence request through navigator.storage.persist() matters. Without this request, the browser can automatically evict data in the Origin Private File System under storage pressure, especially for rarely visited origins. Once a persistence request has been granted, the storage is marked as best effort persistent and only removed through explicit user action. For applications that rely on the Origin Private File System as their primary data store, for example as a substitute for cloud sync, this request is practically mandatory.
// Request persistent storage so OPFS data survives storage pressure
async function ensurePersistentStorage() {
if (navigator.storage && navigator.storage.persist) {
const isPersisted = await navigator.storage.persisted();
if (!isPersisted) {
const granted = await navigator.storage.persist();
console.log("Persistent storage granted:", granted);
}
}
}
// Check remaining quota before writing large files to OPFS
async function checkAvailableSpace(requiredBytes) {
const estimate = await navigator.storage.estimate();
const available = estimate.quota - estimate.usage;
if (available < requiredBytes) {
throw new Error(`Not enough OPFS quota: need ${requiredBytes}, have ${available}`);
}
return available;
}
7. Error handling, locking and browser support
A typical mistake when working with the Origin Private File System is trying to open the same file from two contexts at once through a sync access handle. The second call throws a NoModificationAllowedError exception, because the file is already exclusively locked. Robust applications catch that error and implement a queue, or use the Web Locks API, to access the same file in a coordinated sequence instead of letting the error propagate unhandled.
Regarding browser support, the Origin Private File System has been fully available in Chrome and Edge since version 108, Firefox has supported it since version 111, and Safari caught up with version 17, albeit with some detail differences in the performance of sync access handles. For production use, feature detection on "getDirectory" in navigator.storage is recommended, along with a fallback to IndexedDB for older browsers, so the application does not break entirely but simply runs with reduced performance.
Another pitfall is forgetting to call close() on a sync access handle. If the handle stays open, for example because an exception was thrown before the close() call, the file remains locked for other contexts until the worker terminates. A try/finally block around every sync access handle operation in the Origin Private File System is therefore not a matter of style, it prevents hard to diagnose deadlocks.
8. Performance measurement head to head
To make the performance advantages of the Origin Private File System tangible, a simple benchmark is worthwhile: writing 10,000 small records, once through IndexedDB transactions and once through a sync access handle in the Origin Private File System. In our own measurements on typical desktop configurations, throughput with sync access handles is regularly 5 to 15 times higher, because every IndexedDB transaction brings serialization and commit overhead, while the Origin Private File System allows direct byte writes without a transaction log.
The difference is even more pronounced with sequential reads of large files: while IndexedDB usually has to load blobs completely into memory before they can be processed, the Origin Private File System allows partial reads through byte offsets. An application can therefore read only the required 4 kilobytes from a 500 megabyte file without touching the rest, a pattern that is decisive for video streaming, database indexes or log files in the browser.
9. Origin Private File System at a glance
The following table summarizes when the Origin Private File System is the right choice compared to related browser APIs. The selection is based on data volume, access pattern and whether user interaction with real files is required.
| Requirement | Poor choice | Recommendation | Reason |
|---|---|---|---|
| Large binary data, high throughput | IndexedDB blobs | Origin Private File System | Sync access handles without transaction overhead |
| User opens a local file | Origin Private File System | File System Access API | Permission dialog and real file system path required |
| Small structured objects | Origin Private File System per object | IndexedDB | Indexing and query engine already built in |
| SQL database in the browser | IndexedDB as an object store | SQLite Wasm over Origin Private File System | Real ACID transactions and SQL syntax |
| Access from the main thread without a worker | Sync access handle | createWritable() |
Sync access handles are only allowed in dedicated workers |
The key takeaway from the table: the Origin Private File System is not a replacement for IndexedDB or the File System Access API, but a complement for exactly those cases where file semantics, high throughput and synchronous access inside a worker are needed. Anyone who does not need these three criteria will often get by more easily with IndexedDB and less architectural overhead.
Mironsoft
JavaScript architecture, web storage and performance engineering
Offline capable applications with real file system performance?
We design storage architectures around the Origin Private File System, from worker based sync access handles to SQLite Wasm integrations for data intensive web applications.
Storage architecture
Analysis of whether Origin Private File System, IndexedDB or Cache Storage is the right choice
Worker integration
Sync access handles, message protocols and locking strategies implemented cleanly
Performance audit
Benchmarks and migration strategy from IndexedDB to the Origin Private File System
10. Summary
The Origin Private File System brings real file system semantics to the browser, isolated per origin and without any permission dialog for the user. The decisive difference from IndexedDB lies in the synchronous sync access handles, available only inside dedicated workers, which allow direct, unbuffered reads and writes there without transaction overhead. For Wasm ports like SQLite, for video and image editing, and for every use case with high I/O throughput, the Origin Private File System is currently the fastest available browser storage option.
At the same time, the Origin Private File System is not a universal replacement: for simple structured data that needs indexing, IndexedDB remains the simpler choice, and for accessing a user's real local files the File System Access API is the right tool. Anyone who deliberately deploys all three APIs according to their respective strengths builds applications that stay both performant and maintainable, without unnecessary complexity from the wrong storage choice.
Origin Private File System — the essentials at a glance
Entry point
navigator.storage.getDirectory() returns the root handle of the Origin Private File System, from there create directories and files.
Sync access handle
createSyncAccessHandle() only inside a dedicated worker, synchronous read and write without event loop overhead.
Ideal for
SQLite Wasm, image and video editing, large binary files with high throughput.
Not suited for
Accessing a user's real local files (File System Access API) or simple indexed objects (IndexedDB).