Skip to content

File System Access & OPFS

The browser provides two distinct file-writing surfaces:

  1. File System Access API (showOpenFilePicker, showSaveFilePicker) — lets users pick real files from their local file system. Requires a user gesture (button click). Not available in all browsers.
  2. Origin Private File System (OPFS) — a sandboxed, origin-scoped virtual file system. No user gesture, no file picker, fully programmatic. Works in workers. Quota-managed like IndexedDB.

The picker functions show the native OS file dialog. They must be called from a user-gesture handler.

// Open a file chosen by the user
async function openFile() {
// showOpenFilePicker must be called inside a user gesture (click, etc.)
const [fileHandle] = await window.showOpenFilePicker();
const file = await fileHandle.getFile();
const text = await file.text();
console.log('File contents:', text.slice(0, 200));
}
// Save / overwrite a file
async function saveFile(content) {
const handle = await window.showSaveFilePicker({
suggestedName: 'output.txt',
types: [{ description: 'Text file', accept: { 'text/plain': ['.txt'] } }],
});
const writable = await handle.createWritable();
await writable.write(content);
await writable.close();
}

Because showOpenFilePicker and showSaveFilePicker require a user gesture, they cannot be run autonomously in the StorageRunner above. The OPFS demo below is fully runnable.

OPFS gives every origin a private directory tree. You do not need a user gesture — just call navigator.storage.getDirectory().

// Get the root of the OPFS
const root = await navigator.storage.getDirectory();
// Create or open a file handle
const fileHandle = await root.getFileHandle('notes.txt', { create: true });
// Write to it
const writable = await fileHandle.createWritable();
await writable.write('Hello from OPFS!');
await writable.close();
// Read it back
const file = await fileHandle.getFile();
const text = await file.text();
console.log(text); // 'Hello from OPFS!'
const root = await navigator.storage.getDirectory();
// Create a sub-directory
const subDir = await root.getDirectoryHandle('drafts', { create: true });
// List entries
for await (const [name, handle] of subDir.entries()) {
console.log(handle.kind, name);
}
Browser Storage
Why does showOpenFilePicker require a user gesture?
What does navigator.storage.getDirectory() return?
After calling fileHandle.createWritable() and writing data, what must you do before the data is visible to future reads?
Which of the following correctly describes OPFS storage persistence?