# File System Access API

> showOpenFilePicker()、showSaveFilePicker() 与 showDirectoryPicker() 返回指向真实文件的句柄。本页列出全部选项、它们会以哪些异常拒绝，以及没有选择器时的回退路径。

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 支持。

## 语法

```js
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`。

:::observed
在 Chrome 中，用户激活处理函数之外调用 `showOpenFilePicker()` 会以 `SecurityError: Must be handling a user gesture to show a file picker.` 拒绝；从跨源 iframe 调用会以 `SecurityError: Cross origin sub frames aren't allowed to show a file picker.` 拒绝；传 `{ types: [{ accept: { "image/png": ["png"] } }] }` 会以 `TypeError: Extension 'png' must start with '.'.` 拒绝。这些字符串在 Chromium 的 [`global_file_system_access.cc`](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/file_system_access/global_file_system_access.cc) 中（chromium.googlesource.com）。
:::

## 示例

每个示例都精确检测即将调用的方法；Firefox 与 Safari 虽然为 OPFS 暴露了 `FileSystemFileHandle`，但 `"showOpenFilePicker" in window` 仍为 `false`。

### 打开、编辑并保存文本文件，回退到 input 与下载

有选择器时，用户直接覆盖保存自己打开的文件。没有选择器时，`<input type="file">` 负责读取，点击 `<a download>` 写出一份新副本；回退路径无法覆盖原文件，所以界面应写「下载」而不是「保存」。

```js
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 恢复句柄并重新检查权限

句柄可结构化克隆，所以「最近文件」列表可以直接存句柄。权限不一定保留：先调 `queryPermission()`，只在状态为 `"prompt"` 时才在点击中调 `requestPermission()`。

```js
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）](/zh/reference/manifest/file-handlers/)，文件编辑类应用的另一半：把 PWA 注册为某类文件的打开方式
- [处理文件](/zh/guides/file-handling/)
- [Origin Private File System（OPFS，源私有文件系统）](/zh/reference/storage/opfs/)，同一套句柄 API 中跨引擎、无提示的部分
- [Storage Buckets API](/zh/reference/capabilities/storage-buckets/)
- [File System Access: showOpenFilePicker() method](https://wicg.github.io/file-system-access/#api-showopenfilepicker)（wicg.github.io）
- [File System Standard: FileSystemFileHandle interface](https://fs.spec.whatwg.org/#api-filesystemfilehandle)（fs.spec.whatwg.org）
- [The File System Access API: simplifying access to local files](https://developer.chrome.com/docs/capabilities/web-apis/file-system-access)（developer.chrome.com）