file system access

File System Access API — Live Demos

checking…

Open a real file, edit it, save it back

Open a .txt/.md file from your disk, edit it below, and save changes back to the same file. This is the native-editor round-trip. (Chromium only — else a fallback runs.)

No file open
const [handle] = await window.showOpenFilePicker(); const file = await handle.getFile(); editor.value = await file.text(); // keep `handle` to save back!
--:--:--Click "Open file" to pick a text file from disk

The Save vs Save As distinction

The core concept most tutorials skip. "Save" reuses the handle you already have (no dialog). "Save As" calls showSaveFilePicker() for a brand-new handle.

💾 Save (in place)
reuse existing handle
handle.createWritable()
writable.write(data)
writable.close()
✓ No dialog · writes to original file
📄 Save As (new file)
showSaveFilePicker()
→ NEW handle
handle.createWritable()
write() → close()
↑ Shows dialog · user picks name/location
Atomic write: createWritable() writes to a temp file and only commits on close() — a crash mid-write leaves your original file intact, not corrupted.
// SAVE — reuse the open handle, no dialog async function save(handle, data) { const w = await handle.createWritable(); // temp file await w.write(data); await w.close(); // atomic commit }
--:--:--Use Tab 1 to try Save vs Save As with a real file

Read an entire directory

Pick a folder and iterate its contents with for await...of. Each entry has a kind of 'file' or 'directory'. (Chromium only.)

Directory contents will appear here…
const dir = await window.showDirectoryPicker(); for await (const [name, handle] of dir.entries()) { console.log(name, handle.kind); // 'file' | 'directory' }
--:--:--Pick a folder to list its files and subfolders

Persist handles across sessions (IndexedDB)

Handles are structured-cloneable, so you can store them in IndexedDB and reopen the actual file later. But permission isn't persisted — you must re-check on return.

No handle stored
// Store the handle await set('last-file', fileHandle); // IndexedDB // On return — permission NOT remembered, re-request it if (await handle.queryPermission({mode:'readwrite'}) !== 'granted') await handle.requestPermission({mode:'readwrite'});
⚠ Reading a persisted handle requires re-granting permission via a user gesture — the browser never silently restores file access.
--:--:--Open & remember a file, then Recall it to see the permission re-check

Origin Private File System — no picker, no prompt

A sandboxed, origin-private store via navigator.storage.getDirectory(). No permission prompt, invisible to the user's file manager, and supported in all modern browsers (unlike the pickers).

OPFS contents:
Click "List files" to read the OPFS root…
const root = await navigator.storage.getDirectory(); // no prompt! const fh = await root.getFileHandle('notes.txt', {create:true}); const w = await fh.createWritable(); await w.write(text); await w.close();
--:--:--Write a file to OPFS, then list — it persists with no permission prompt