The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Choose files | const [handle] = await showOpenFilePicker({ multiple: false, types }) | View examples |
| Choose a save destination | const handle = await showSaveFilePicker({ suggestedName: 'report.txt', types }) | View examples |
| Choose a directory | const directory = await showDirectoryPicker({ mode: 'readwrite' }) | View examples |
| Query handle permission | const state = await handle.queryPermission({ mode: 'readwrite' }) | View examples |
| Request handle permission | const state = await handle.requestPermission({ mode: 'readwrite' }) | View examples |
| Read a file snapshot | const file = await handle.getFile(); const text = await file.text() | View examples |
| Open a writable stream | const writable = await handle.createWritable({ keepExistingData: true }) | View examples |
| Write and commit | await writable.write(contents); await writable.close() | View examples |
| Get or create a child file | const file = await directory.getFileHandle('notes.txt', { create: true }) | View examples |
| Iterate directory entries | for await (const [name, handle] of directory.entries()) inspect(name, handle) | View examples |
| Remove a directory entry | await directory.removeEntry('cache', { recursive: true }) | View examples |
| Open the OPFS root | const root = await navigator.storage.getDirectory() | View examples |
| Open worker synchronous access | const access = await fileHandle.createSyncAccessHandle() | View examples |
| Estimate storage usage | const { usage, quota } = await navigator.storage.estimate() | View examples |
| Request persistent storage | const persistent = await navigator.storage.persist() | View examples |
The File System API exposes two different surfaces: picker-acquired handles to user-visible files where supported, and an origin-private filesystem reached through navigator.storage. Picker APIs require a secure top-level context, transient activation, and explicit user selection. OPFS is private, quota-managed application storage—not a path into the user's filesystem. Feature-test every capability and design for revocation, external edits, quota failure, and data deletion.
Step by step
Detailed examples
Open pickers only from explicit user actions
showOpenFilePicker(), showSaveFilePicker(), and showDirectoryPicker() are window methods for secure contexts and require transient user activation. Browser support varies, and pickers may reject with AbortError when canceled or SecurityError when disallowed. MIME types and extensions are hints, not content validation. Never upload, overwrite, or recursively scan immediately after selection without showing scope and obtaining the appropriate user intent. Provide input type=file and download fallbacks.
async function chooseTextFile() {
if (!window.showOpenFilePicker) return chooseWithFileInput();
try {
const [handle] = await showOpenFilePicker({
types: [{ description: 'Text', accept: { 'text/plain': ['.txt', '.md'] } }],
});
return handle;
} catch (error) {
if (error.name === 'AbortError') return null;
throw error;
}
} Recheck handle permission at the moment of use
A handle may remain structured-clonable and persistable in IndexedDB while its effective permission returns to prompt or denied. Read and readwrite are separate modes. queryPermission() does not prompt; requestPermission() may require a fresh activation and is not available uniformly. Store handles only after explaining the convenience, remove stale records, and never treat possession of a serialized handle as authorization. The user or platform can revoke access outside the app.
async function requireWrite(handle) {
const options = { mode: 'readwrite' };
if (await handle.queryPermission?.(options) === 'granted') return true;
if (!handle.requestPermission) return false;
return await handle.requestPermission(options) === 'granted';
} Read fresh snapshots and commit writable streams
getFile() returns a File snapshot whose data can become unreadable after the disk file changes; request another snapshot to refresh. createWritable() typically writes through a temporary file and commits on close, but this does not provide multi-process transaction isolation. keepExistingData false permits replacing content efficiently; true is needed for seek and partial editing. Always await close(), call abort() on failure where available, handle quota and permission errors, and coordinate same-app writers.
async function saveText(handle, text) {
if (!await requireWrite(handle)) throw new DOMException('Write access denied', 'NotAllowedError');
const writable = await handle.createWritable();
try {
await writable.write(new TextEncoder().encode(text));
await writable.close();
} catch (error) {
await writable.abort?.(error).catch(() => {});
throw error;
}
} Traverse selected directories with explicit bounds
Directory handles provide async values(), keys(), and entries(), plus getFileHandle(), getDirectoryHandle(), removeEntry(), and resolve(). Child names are single path components; reject user-generated separators and dot segments rather than pretending they are paths. Iteration can be large and entries can change while scanning, so yield progress, cap depth and count, and allow cancellation. Recursive deletion is irreversible from the API perspective and needs confirmation specific to the selected subtree.
async function listChildren(directory, limit = 500) {
const children = [];
for await (const [name, handle] of directory.entries()) {
children.push({ name, kind: handle.kind });
if (children.length >= limit) break;
}
return children;
} Use OPFS for app-private data and sync access only in workers
navigator.storage.getDirectory() returns the origin-private root of the current storage bucket. OPFS filenames are not user-visible paths and data can be cleared with site data or evicted unless storage persistence policy protects it. createSyncAccessHandle() is for OPFS files in dedicated workers and generally grants exclusive access to that file until close; it enables synchronous read, write, truncate, getSize, and flush without blocking the page. Always close it in finally.
const root = await navigator.storage.getDirectory();
const file = await root.getFileHandle('database.bin', { create: true });
const access = await file.createSyncAccessHandle();
try {
const bytes = new TextEncoder().encode('CMDM');
access.write(bytes, { at: 0 });
access.truncate(bytes.length);
access.flush();
} finally { access.close(); } Plan for quota, eviction, deletion, and origin isolation
estimate() reports approximate usage and quota and is not a promise that a future write will succeed. persist() requests reduced eviction risk, but the browser decides and users can still clear data. OPFS is isolated by origin and storage partitioning, not encrypted against same-origin script, browser extensions, malware, or device access. Validate content after reading, encrypt only through a reviewed key-management design, expose data export and deletion, and handle QuotaExceededError without destructive retry loops.
async function storageStatus() {
const { usage = 0, quota = 0 } = await navigator.storage.estimate();
const persistent = await navigator.storage.persisted?.() ?? false;
return { usage, quota, availableEstimate: Math.max(0, quota - usage), persistent };
} Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



