Lesson 0002 · ~12 minutes

Root, handles & CRUD

One skill: open the OPFS root, get file/directory handles, and perform main-thread create / read / write / delete — without confusing this with user-visible pickers or worker sync I/O.

Win for this lesson

You can sketch getDirectory → getFileHandle → createWritable / getFile, know how folders nest, and pick the right delete path — still without sync access handles.

1. One root per origin

Entry point (no picker, no permission prompt for OPFS itself): navigator.storage.getDirectory() returns a FileSystemDirectoryHandle for the origin’s private root (web.dev OPFS):

const root = await navigator.storage.getDirectory();
// root.kind === "directory", root.name === ""

Same root for every page (and worker) on that origin. Clearing site data clears OPFS. This is still not a path under the user’s Desktop.

Default rule (steal this)

Treat the root as an app-owned disk: design a folder layout (e.g. exports/, media/) instead of dumping every blob at "". Names are strings you invent — version or namespace them if schemas evolve.

2. Handles: create and open

You never pass OS paths. You walk handles. On a directory handle (MDN FileSystemDirectoryHandle):

getFileHandle

Child file by name. Pass { create: true } to create if missing; omit to open existing only (rejects if absent).

getDirectoryHandle

Child folder by name. Same { create: true } pattern. Nest by calling again on the child directory.

const root = await navigator.storage.getDirectory();

const fileHandle = await root.getFileHandle("notes.txt", { create: true });
const folder = await root.getDirectoryHandle("exports", { create: true });
const nested = await folder.getFileHandle("report.pdf", { create: true });

// Existing only — fails if missing
const existing = await root.getFileHandle("notes.txt");

A FileSystemFileHandle is a capability to that file, not the bytes themselves. You still need read/write steps.

3. Read and write (async main thread)

Main-thread (and worker-safe async) path from web.dev / MDN:

Job Call Notes
Read bytes await fileHandle.getFile() Returns a File (a Blob). Then text(), arrayBuffer(), stream().
Write bytes createWritable()write(...)close() close() persists. Without close, don’t assume data stuck.
const handle = await root.getFileHandle("notes.txt", { create: true });

// Write
const writable = await handle.createWritable();
await writable.write("hello OPFS");
await writable.close();

// Read
const file = await handle.getFile();
console.log(await file.text()); // "hello OPFS"
Not this lesson: sync access handles

createSyncAccessHandle() is the high-throughput, in-place path for dedicated workers (Wasm/DB engines). Different mental model — later lesson. Do not reach for it for ordinary main-thread text/blob writes (MDN).

4. Delete and list

Two delete styles (web.dev OPFS):

await fileHandle.remove();
await folder.remove({ recursive: true });
await root.removeEntry("notes.txt");

// Nuclear: wipe whole OPFS
// await (await navigator.storage.getDirectory()).remove({ recursive: true });

List children: directory handles are async-iterable (MDN):

for await (const [name, handle] of root.entries()) {
  console.log(name, handle.kind); // "file" | "directory"
}

5. Practice — pick the call

Choose the best default. Equal-length options so formatting doesn’t hint. Feedback is immediate.

Scenario A

You need the origin’s private OPFS root handle (create nothing else yet).

Scenario B

Under the OPFS root, create draft.bin if missing and get its file handle.

Scenario C

On the main thread, replace the entire contents of an existing OPFS file handle with a short string.

Scenario D

Read the full text of a file you already have a FileSystemFileHandle for.

Scenario E

You have the parent folder handle and the child name tmp.dat. Delete that child only.

6. What to remember

Ask your teacher Fuzzy bits — move/rename, concurrent writers, “File vs handle” — ask in chat. Follow-ups are part of the method.

Primary source (read next)

web.dev — The origin private file system (Thomas Steiner). Best short primary for this lesson’s handle map. Still useful (deferred from lesson 1): Storage for the web for the spectrum you already practiced.