The File System Access API for native file read/write
Published December 11, 2026
// TypeScript's DOM lib has FileSystemFileHandle but not window.showOpenFilePicker
// itself yet -- narrow cast for just the missing entry point.
type ShowOpenFilePicker = (opts: {
types: { description: string; accept: Record<string, string[]> }[];
}) => Promise<FileSystemFileHandle[]>;
let fileHandle: FileSystemFileHandle | undefined;
export const openTextFile = async () => {
const showOpenFilePicker = (window as unknown as { showOpenFilePicker: ShowOpenFilePicker }).showOpenFilePicker;
[fileHandle] = await showOpenFilePicker({
types: [{ description: "Text", accept: { "text/plain": [".txt"] } }],
});
const file = await fileHandle.getFile();
return file.text();
};
export const saveTextFile = async (contents: string) => {
if (!fileHandle) throw new Error("Open a file before saving");
const writable = await fileHandle.createWritable();
await writable.write(contents);
await writable.close();
};What
window.showOpenFilePicker() returns a FileSystemFileHandle for a file the user picked, and handle.createWritable() opens a stream you can write back to the same file — a real edit-and-save loop, not a download.
Why it matters
A plain <input type="file"> gives you a one-shot, read-only snapshot of a file: you can read its contents, but "saving" means generating a new Blob and triggering a download to wherever the browser's download folder is, disconnected from the original file. That's fine for a one-time import, but it can't support a "Save" button that overwrites the file the user opened — every browser-based text/image/code editor that supports real save-in-place (not "download a copy") is built on this API.
The handle you get back from showOpenFilePicker() is a durable reference: hold onto it, and createWritable() gives you a FileSystemWritableFileStream that writes straight to that path on disk, no picker prompt for the write itself (the browser may re-prompt for permission if it lapses, but not for the destination).
How it works
showOpenFilePicker({ types })restricts the picker to a file type and returns an array of handles even for a single-file pick (default ismultiple: false), hence the destructuring assignment.fileHandle.getFile()returns aFileobject — the same interface<input type="file">gives you — so.text()/.arrayBuffer()work identically.createWritable()on the same handle opens a stream targeting the original file.write()accepts a string,Blob, orArrayBufferdirectly.close()is what actually commits the write and releases the lock — a writable stream that's never closed leaves the file either unchanged or in an undefined partial state, so this isn't optional cleanup.- The guard in
saveTextFile— throwing if there's no open handle — exists because callingcreateWritable()onundefinedis a real mistake a "Save" button wired up before "Open" will make.
Gotchas
- This is a Chromium-only API. Firefox and Safari do not implement
showOpenFilePicker,showSaveFilePicker, orshowDirectoryPickerin any released version, and neither engine has signaled plans to. They only support the Origin Private File System (navigator.storage.getDirectory()), a sandboxed filesystem the user never sees — a fundamentally different feature that doesn't touch real files on disk. - Because of that gap, this API is unsuitable as your only file I/O path for anything that needs to work outside Chromium — pair it with an
<input type="file">+ download fallback, feature-detected via"showOpenFilePicker" in window. - TypeScript's default
lib.dom.d.tssupport for these types varies by TypeScript version; ifFileSystemFileHandleorshowOpenFilePickeraren't recognized, check your TS version before assuming a runtime problem. - Permission to a previously-granted handle can lapse (e.g., after the tab is reloaded); calling
createWritable()on a stale handle can reject, so production code needs aqueryPermission()/requestPermission()check the snippet omits for brevity.
Related
<input type="file">— the universally-supported, read-only, download-to-save alternative this API is an upgrade over, on the engines that support it.- The Origin Private File System — Firefox and Safari's sandboxed alternative; good for app-private storage, not for editing a user's real files.