JavaScript File System Access Table

PieceWhat it doesField note
showOpenFilePicker()Pick + HANDLEReturns a FileSystemFileHandle - re-readable without re-prompting
handle.getFile()Read via handleA fresh File each call - the content may have changed on disk
showSaveFilePicker()Pick where to savecreateWritable() writes - real saves, not download copies
showDirectoryPicker()A whole folder handleIterate entries - editors that open projects
Handles are serializablePersist in IndexedDBRe-prompt once on restore - then the same file again
Chromium-onlyThe support realitySafari/Firefox: no - feature-detect, fall back to input+download
vs File API inputOne-shot vs handleinput gives a File copy; handles give a LIVING file
Permission per writeThe ask modelRead once; writes re-confirm - user keeps control
Reference: the MDN File System Access reference. The File System Access API upgrades one-shot File picking to LIVING FILE HANDLES: showOpenFilePicker returns a handle you can re-read, write to (createWritable), and even persist in IndexedDB - the editor that reopens your file next session is this API. The trade: Chromium-only, so the input-plus-download pattern remains the universal fallback, feature-detected. Bottom line: handles turn 'upload a copy' into 'edit this file' - the difference between a form and an application. Related tools: file API table (the one-shot baseline), typed arrays table (the writable streams), and localStorage table (where handles persist).

The File System Access API upgrades one-shot File picking to LIVING FILE HANDLES: showOpenFilePicker returns a handle you can re-read, write to via createWritable, and even persist in IndexedDB - the editor that reopens your file next session is this API.

Bottom line: handles turn 'upload a copy' into 'edit this file' - the difference between a form and an application. The trade: Chromium-only, so the input-plus-download pattern remains the universal fallback, behind a feature-detect.

The honest part: the permission model is graduated - the read prompt covers reads, but writes re-confirm (the browser asks again on first write), keeping the user in control of the destructive-capability boundary even after they picked the file.

How to use

  1. Open with a handle: const [handle] = await showOpenFilePicker(); const file = await handle.getFile() - keep the handle for later saves.
  2. Save in place: const writable = await handle.createWritable(); await writable.write(data); await writable.close() - the real file updates, no download.
  3. Persist the handle: store it in IndexedDB, re-prompt once on restore with requestPermission({mode: 'readwrite'}) - the same file again next session.

Frequently asked questions

What does a FileSystemHandle give that a File from an input does not?

Liveness and capability. The input File is a SNAPSHOT: a copy of the bytes at pick time - save means downloading a new file, and edits to the original on disk are invisible. A handle is a REFERENCE to the living file: getFile() re-reads fresh bytes on demand (picking up external changes), createWritable() writes back to the real location, and queryPermission/requestPermission manage ongoing capability. The architectural consequence: apps built on handles behave like desktop editors (open, edit, save in place, reopen later) while input-built apps remain import-export tools. Both have their place; handles are the application-shaped one.

How does handle persistence across sessions work?

IndexedDB stores handles - they are structured-cloneable objects, so an editor saves the handle alongside the document metadata. On restore, the handle comes back but its permissions do NOT: requestPermission({mode: 'readwrite'}) re-asks once (a lighter prompt referencing the known file), and then the same file is live again. The UX win: 'reopen report.md' instead of a file picker treasure hunt. The caveats: the user can always deny on restore (handle the rejection as 'open another file'), and persisted handles can be revoked from browser site settings - the permission model still belongs to the user.

Why is write access re-confirmed separately from read?

The destructive boundary is different. Reading a file exposes data; writing REPLACES it - the irreversible action. So the API graduates: the open prompt grants reads of the picked file, and the first createWritable triggers a separate save confirmation - the user approves writing to THAT file. This is why scripts cannot silently corrupt a picked file even after legitimate reads. For apps, design for the ask: it fires once per handle per session typically, and queryPermission({mode: 'readwrite'}) lets you check before attempting so the write flow can explain itself before requesting.

How should a product handle the Chromium-only reality?

Two implementations behind one feature-detect. if (window.showOpenFilePicker) - the handle path (edit-in-place, reopen, real saves); else - the classic path: <input type=file> for open, download via blob URL or the File System Access-less save for export. The product stays functional everywhere and gains powers on Chromium. The honesty rule: do not advertise 'save' when the fallback downloads copies - label it 'export' in fallback mode, because the mental models differ (in-place edit vs new file in Downloads). Many apps shipped for years with just the fallback; the API is the upgrade tier, not the baseline.

Related tools