能力 · API
File System Access API
发布于 更新于
File System Access API 让页面打开、编辑并保存用户设备上的真实文件和文件夹:showOpenFilePicker()、showSaveFilePicker() 与 showDirectoryPicker() 返回 FileSystemFileHandle 和 FileSystemDirectoryHandle 对象,它们在选择器关闭后仍然有效,可以存进 IndexedDB,并通过 createWritable() 写回同一个文件。句柄接口本身由 WHATWG File System Standard 定义,与 Origin Private File System 共用。
选择器只有 Chromium 实现:桌面端的 Chrome 86 与 Edge 86。Firefox 111 与 Safari 15.2 为 OPFS 实现了句柄接口但不提供选择器,Safari 直到 Safari 26 才加入 createWritable()(BCD api.Window.showOpenFilePicker、api.FileSystemFileHandle.createWritable)。句柄上的 queryPermission() 与 requestPermission() 只有 Chrome 86 支持。
window.showOpenFilePicker()window.showOpenFilePicker(options)
window.showSaveFilePicker()window.showSaveFilePicker(options)
window.showDirectoryPicker()window.showDirectoryPicker(options)showOpenFilePicker() 返回 Promise<sequence<FileSystemFileHandle>>(除非 multiple 为 true,否则只有一个元素);showSaveFilePicker() 返回 Promise<FileSystemFileHandle>;showDirectoryPicker() 返回 Promise<FileSystemDirectoryHandle>。三者都带 [SecureContext],并要求瞬时用户激活。保存选择器返回的句柄已带读写权限,而且 showSaveFilePicker() 在 Promise 兑现的那一刻就清空所选文件,不是等你之后 close() 可写流时。
每个选择器接受一个可选的 options 字典。OpenFilePickerOptions 与 SaveFilePickerOptions 继承共享的 FilePickerOptions;DirectoryPickerOptions 独立定义。
| 成员 | 类型 | 必填 | 适用于 | 说明 |
|---|---|---|---|---|
types |
sequence<FilePickerAcceptType> |
否 | open、save | 供用户选择的过滤器。每一项有 description(USVString,默认 "")和一个 accept 记录,把 MIME 类型映射到一个后缀或后缀列表,例如 { "text/plain": [".txt", ".md"] }。 |
excludeAcceptAllOption |
boolean |
否 | open、save | 默认 false。为 true 时省略「所有文件」过滤器;types 为空时无论如何都会加上。 |
id |
DOMString |
否 | 全部 | 最多 32 个字符,只能是字母、数字、_ 或 -。浏览器按源记住该 id 上次使用的目录,下次从那里开始。 |
startIn |
WellKnownDirectory 或 FileSystemHandle |
否 | 全部 | 起始位置:"desktop"、"documents"、"downloads"、"music"、"pictures"、"videos" 之一,或之前选择得到的句柄。id 记住的目录优先。 |
multiple |
boolean |
否 | open | 默认 false。为 true 时用户可选任意数量的文件。 |
suggestedName |
USVString? |
否 | save | 预填文件名;浏览器可能清洗或忽略它认为危险的名字。 |
mode |
FileSystemPermissionMode |
否 | directory | "read"(默认)或 "readwrite"。用 "readwrite" 时权限提示一并覆盖写入,所选目录不需要第二次提示。 |
accept 中的后缀必须以 . 开头、不能以 . 结尾、最长 16 个字符,并且只能包含合法的后缀码点。
选择器按下列顺序拒绝;SecurityError 与 TypeError 两类在任何对话框出现之前检查。
| 异常 | 条件 |
|---|---|
SecurityError |
文档的源是 opaque origin(沙箱 iframe);或其源与顶层源不同(跨源 iframe);或窗口没有瞬时用户激活。 |
TypeError |
accept 的键不是合法 MIME 类型或带参数;某个后缀违反上述规则;types 加上「所有文件」选项后仍没有任何过滤器;id 超过 32 个字符或含其他字符。 |
AbortError |
用户未选择就关闭了对话框;或浏览器判定所选内容过于敏感(系统文件夹、整个下载目录)并选择拒绝而非重开对话框;或对 showDirectoryPicker() 而言,权限请求的结果不是 "granted"。 |
之后对返回句柄的操作抛的是 File System Standard 的错误:权限未授予时为 NotAllowedError,文件被删除或移动时为 NotFoundError,另一个可写流或同步访问句柄持有锁时为 NoModificationAllowedError。
每个示例都精确检测即将调用的方法;Firefox 与 Safari 虽然为 OPFS 暴露了 FileSystemFileHandle,但 "showOpenFilePicker" in window 仍为 false。
打开、编辑并保存文本文件,回退到 input 与下载
Section titled “打开、编辑并保存文本文件,回退到 input 与下载”有选择器时,用户直接覆盖保存自己打开的文件。没有选择器时,<input type="file"> 负责读取,点击 <a download> 写出一份新副本;回退路径无法覆盖原文件,所以界面应写「下载」而不是「保存」。
let fileHandle = null;const editor = document.querySelector("#editor");const legacyInput = document.querySelector("#legacy-file");const supported = "showOpenFilePicker" in window;
document.querySelector("#open").addEventListener("click", async () => { if (!supported) { legacyInput.click(); return; } try { [fileHandle] = await window.showOpenFilePicker({ types: [{ description: "文本", accept: { "text/plain": [".txt", ".md"] } }], }); editor.value = await (await fileHandle.getFile()).text(); } catch (err) { if (err.name !== "AbortError") throw err; // 用户关闭对话框是正常情况 }});
legacyInput.addEventListener("change", async () => { editor.value = await legacyInput.files[0].text();});
document.querySelector("#save").addEventListener("click", async () => { if (!supported) { const link = document.createElement("a"); link.href = URL.createObjectURL(new Blob([editor.value], { type: "text/plain" })); link.download = "document.txt"; link.click(); URL.revokeObjectURL(link.href); return; } fileHandle ??= await window.showSaveFilePicker({ suggestedName: "document.txt" }); const writable = await fileHandle.createWritable(); await writable.write(editor.value); await writable.close(); // 磁盘上的文件只在这一步改变});写入先进入临时文件,close() 兑现时才替换目标;在 close() 之前关闭标签页,原文件保持原样。
从 IndexedDB 恢复句柄并重新检查权限
Section titled “从 IndexedDB 恢复句柄并重新检查权限”句柄可结构化克隆,所以「最近文件」列表可以直接存句柄。权限不一定保留:先调 queryPermission(),只在状态为 "prompt" 时才在点击中调 requestPermission()。
async function reopen(db, button) { if (!("showOpenFilePicker" in window)) return null;
const handle = await db.get("recent", "last"); // 之前选择时存下的文件句柄 if (!handle) return null;
const mode = { mode: "readwrite" }; let state = await handle.queryPermission(mode); if (state === "prompt") { await new Promise((resolve) => button.addEventListener("click", resolve, { once: true })); state = await handle.requestPermission(mode); } return state === "granted" ? handle : null;}返回 null 时调用方回到选择器;"denied" 状态会一直保持,直到用户在站点设置中自行更改。
- Manifest 文件处理程序(file_handlers),文件编辑类应用的另一半:把 PWA 注册为某类文件的打开方式
- 处理文件
- Origin Private File System(OPFS,源私有文件系统),同一套句柄 API 中跨引擎、无提示的部分
- Storage Buckets API
- File System Access: showOpenFilePicker() method(wicg.github.io)
- File System Standard: FileSystemFileHandle interface(fs.spec.whatwg.org)
- The File System Access API: simplifying access to local files(developer.chrome.com)
规范
- 图例
- 支持
- 部分支持
- 需开启标志
- 不支持
- 未知
| 浏览器 / 平台 | 支持 | 版本 | 置信度 | 来源 | 备注 |
|---|---|---|---|---|---|
| Chrome (Android) | 不支持 | — | 中 | 来源 | 1 |
| Chrome (Desktop) | 支持 | 86 | 中 | 来源 | — |
| Edge (Desktop) | 支持 | 86 | 中 | 来源 | — |
| Safari (iOS) | 不支持 | — | 中 | 来源 | 2 |
| Safari (macOS) | 不支持 | — | 中 | 来源 | 3 |
| Firefox (Desktop) | 不支持 | — | 中 | 来源 | 4 |
| Samsung Internet | 不支持 | — | 中 | 来源 | 5 |
- Android 上不暴露 showOpenFilePicker / showSaveFilePicker。
- 仅提供源私有文件系统(OPFS),没有用户可见的文件选择器。
- 没有 showOpenFilePicker;仅 OPFS。
- 仅 OPFS;无法访问用户可见的文件选择器。
- 选择器方法(`showOpenFilePicker`、`showSaveFilePicker`、`showDirectoryPicker`)在 Chromium 中仅限桌面端;Chrome for Android 没有实现,Samsung Internet 也就无从镜像(MDN 兼容性表,2026-10-03 核对)。