Lesson 0001 · ~10 minutes
One skill: place the Origin Private File System on the client storage spectrum and pick it only when the hard problem is file-shaped bytes private to the origin (editors, media pipelines, large blobs treated as files).
After the quiz, you can defend “OPFS yes / no” in a design review
without writing a line of getDirectory yet — and without
confusing it with IndexedDB, Cache Storage, or user-visible file
pickers.
Some product data is not “a row with keys” and not “an HTTP response.” It is a file: a PDF export, a multi-MB media buffer, a SQLite database file, a directory of project assets. You want create / read / write / delete, nested folders, and eventually high-throughput byte I/O — still inside the browser’s security sandbox.
The
Origin Private File System (OPFS)
is the browser’s main answer for that shape: an
origin-scoped
virtual filesystem that is
not visible in Finder/Explorer. You enter it with
navigator.storage.getDirectory(), which returns a root
FileSystemDirectoryHandle.
web.dev
’s default split is blunt: for network resources needed to
load the app, use Cache Storage; for
file-based content, use OPFS; for other app data, use
IndexedDB.
OPFS is available from windows, workers, and service workers. A sync access handle path exists for maximum throughput in dedicated workers (the reason Wasm databases love OPFS) — that depth comes later. Today only need the job description.
Three common confusions:
Request/Response pairs for load/offline
shells. Putting arbitrary blobs into fake Responses is the wrong
model when the product already thinks in files and paths.
showOpenFilePicker open real user files with permission
prompts and OS security checks. That is the
File System Access
path — great for “edit my document on disk,” different from a private
app sandbox. OPFS has
no user file picker for its root; it is private and can be
wiped with site data
(web.dev OPFS).
Mental model: OPFS is a private disk folder for your origin that only your site’s code can see. Users do not browse it; they experience features built on top of it (exports, offline media, local engines).
Same map you used in IndexedDB and Cache API — steal again from Storage for the web. Today the spotlight is the OPFS row.
React/Vue state, module variables. Fastest. Dies on refresh. Fine when re-fetch or re-derive is cheap.
Tab-scoped string map; survives reloads in that tab, not other tabs. Sync API; tiny; not for workers. Prefer sparingly.
Origin-scoped string map that persists. Sync on main thread; ~few MB; no workers. Avoid for bulk or hot-path structured data.
Tiny values often sent on every HTTP request. Session/auth use-cases only — not a general client database or file store.
Large structured app data: drafts, entity caches, offline queues, user-generated records. Async; keys + indexes; works off-main-thread.
Request/Response pairs — HTML/JS/CSS/images and other HTTP resources for load/offline shells and runtime resource caching.
File-shaped bytes on an origin-private filesystem. Editors, media pipelines, “this is a file” models, high-throughput worker I/O — not the first stop for HTTP shells or keyed records.
If the hard part is “store and manipulate file-shaped content private to this origin”, start with OPFS. If the hard part is “persist structured application data so we can show or edit records without a round trip”, that is IndexedDB. If the hard part is “make the app shell and static assets load offline”, that is the Cache API (usually with a service worker). If the hard part is “open/save a file the user can see in Finder”, that is File System Access pickers, not OPFS. If the hard part is “truth lives on the server and re-fetch is fine”, prefer no durable client store.
| Signal | Leans OPFS | Leans away |
|---|---|---|
| Shape | Files, folders, large blobs, byte ranges | Keyed objects, HTTP Request/Response pairs |
| Job | Local file pipelines; high-throughput I/O | Query/filter entities; offline app shell |
| Visibility | Private to origin; no OS explorer | User must pick a real path on disk |
| Classic fits | Offline media cache-as-files, export packages, Wasm/SQLite DB file, project asset trees |
Multi-paragraph draft rows, CRM contacts, theme toggle,
offline app.js shell
|
Opening the root directory is not the hard part. What bites teams later: quota / eviction (OPFS is still best-effort storage unless you request persistence), main-thread vs worker (sync access handles are for dedicated workers), debugging (DevTools OPFS support is uneven), and assuming private files are “safe forever” or multi-device truth. We’ll drill those later. Today only know they exist so you don’t oversell “just use OPFS.”
Choose the best default. Equal-length options so formatting doesn’t hint. Feedback is immediate.
Podcast app: store multi-MB episode audio offline as files so a worker can stream bytes without re-downloading.
Writing app: multi-paragraph drafts must survive refresh and work offline until the user hits Publish.
PWA shell: HTML, CSS, and JS must load so the UI appears when the network is completely gone.
Desktop-class editor: user opens a real .docx from disk, edits, and saves back to the same path they chose.
Internal admin: always online; table of 20 rows from REST; no offline requirement; refresh is acceptable.
getDirectory call.
web.dev — Storage for the web (spectrum and defaults). Then skim web.dev — The origin private file system so the next lesson’s handle API vocabulary isn’t new twice. MDN’s OPFS overview is the short normative companion.