TL

Origin Private File SystemとFile System Access APIの内部

ブラウザでネイティブ並みのファイルI/Oが扱える理由が分かる。OPFS の隔離ツリーと同期アクセスハンドルの高速書き込み、ユーザー可視 FS への権限モデルを内部動作から正確に整理します。

応用ブラウザOPFSFile System Accessストレージフロントエンド最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.OPFS はオリジンごとに隔離された不可視のファイルツリーで、ユーザーのファイルシステムとは別空間。ディレクトリ/ファイルハンドルでアクセスする。
  • 2.ワーカー上の createSyncAccessHandle は同期 read/write/flush を提供し、コピーや構造化複製を介さない最速の I/O 経路になる。
  • 3.ユーザー可視 FS への showOpenFilePicker 等はユーザー操作(一時的活性化)を起点に権限を得る方式で、OPFS とは権限モデルがまったく異なる。

2つの「ファイルシステム」を混同しない

File System API には、性質のまったく異なる2つの世界が同居しています。1つは Origin Private File System(OPFS)、もう1つは showOpenFilePicker などによるユーザー可視ファイルシステムへのアクセスです。両者は同じハンドル型(FileSystemDirectoryHandle / FileSystemFileHandle)を共有しますが、保存場所も権限モデルも性能特性も別物です。

観点OPFSユーザー可視 FS(File System Access)
保存場所ブラウザ管理の隔離領域(ユーザーには見えない)実際のディスク上のユーザーファイル
入口navigator.storage.getDirectory()showOpenFilePicker / showSaveFilePicker / showDirectoryPicker
権限オリジンに暗黙付与(プロンプト不要)ユーザー操作を起点に明示許可が必要
同期 I/Oワーカーで可(SyncAccessHandle)不可
対応状況主要ブラウザで広く実装Chromium 中心、Safari は OPFS のみ

混乱の元は「同じ API 名前空間に乗っている」ことです。実装上は、OPFS はブラウザのストレージ容量管理(クォータ)の配下にある内部ストレージで、ユーザー可視 FS は OS のファイルダイアログとパーミッションを介した外部リソースです。以降、この区別を軸に内部を見ていきます。

OPFS:オリジンに隔離されたファイルツリー

OPFS は、各オリジンに割り当てられた専用のルートディレクトリを起点とする階層ファイルツリーです。入口は1つだけです。

// OPFS のルート(FileSystemDirectoryHandle)を取得
const root = await navigator.storage.getDirectory();

// サブディレクトリとファイルを作る(create: true で無ければ作成)
const dir = await root.getDirectoryHandle("logs", { create: true });
const file = await dir.getFileHandle("2026-06.bin", { create: true });

ここで返るルートは、localStorage のキー空間や IndexedDB のオブジェクトストアと同じく、オリジン単位で完全に分離されています。別オリジンの OPFS は見えず、同一オリジンポリシーがそのまま境界になります。ユーザーは OS のファイルマネージャからこのツリーを直接たどることはできず、ブラウザがバックエンド(実体はディスク上のどこか、ただしファイル名や構造はブラウザの内部表現)として管理します。

OPFS が他のストレージと根本的に違うのは、バイト列としてのファイルを直接扱える点です。IndexedDB が構造化複製を介したレコードの出し入れであるのに対し、OPFS は「ファイルの任意オフセットに任意バイトを書く」という低レベル操作を提供します。これが SQLite の WASM ビルドや、独自フォーマットの大容量データを扱うアプリで OPFS が選ばれる理由です。

権限プロンプトが要らない理由

OPFS はユーザーのファイルを触りません。あくまでブラウザが各オリジンに割り当てた砂場(サンドボックス)であり、漏れ出す先がないため許可ダイアログ無しで使えます。プライバシー上の判断材料がないので、権限は「オリジンであること」だけで足ります。

SyncAccessHandle:最速 I/O 経路の正体

OPFS の性能上の主役が createSyncAccessHandle() です。これはWeb ワーカー上でのみ取得でき、メインスレッドでは InvalidStateError になります。返ってくる FileSystemSyncAccessHandle は、名前のとおり同期のメソッド群を持ちます。

// 必ずワーカー(DedicatedWorker 等)内で実行する
const root = await navigator.storage.getDirectory();
const fh = await root.getFileHandle("db.sqlite", { create: true });
const access = await fh.createSyncAccessHandle();

const buf = new Uint8Array([0x53, 0x51, 0x4c]);
access.write(buf, { at: 0 });   // 同期。Promise ではなく書き込んだバイト数を返す
const out = new Uint8Array(3);
access.read(out, { at: 0 });     // 同期。読み込んだバイト数を返す
access.flush();                  // ディスクへ確実に反映
access.truncate(0);              // サイズ変更
access.close();                  // ハンドルを解放(同一ファイルの排他ロックを解く)

なぜこれが速いのか。理由は3つあります。

  1. 同期呼び出しread / write がイベントループのタスクを介さず、その場で値を返す。1操作ごとに イベントループ のターンを跨ぐ非同期 API のオーバーヘッドが無い。
  2. コピーの最小化:渡す ArrayBufferTypedArray のバイト列を、構造化複製や JSON 変換を挟まずほぼそのまま I/O に流せる。バッファの中身については TypedArray とメモリ の理解がそのまま効く。
  3. オフセット指定 I/Oat でファイル内の任意位置を直接読み書きできるため、ファイル全体を読み直さずに部分更新できる。

同期 API がワーカー限定なのは設計上の必然です。同期 I/O はその呼び出しの間スレッドをブロックするため、UI を動かすメインスレッドで許すとフリーズを招きます。ワーカーは専用スレッドなので、ブロックしても UI に波及しません。

排他ロックと「開きっぱなし」

createSyncAccessHandle() は対象ファイルに排他ロックを取ります。同じファイルに対して2つ目のハンドルを開こうとすると NoModificationAllowedError で失敗します。処理が終わったら必ず close() を呼んでロックを解放してください。ワーカーを使い捨てにする設計でも、close 漏れは次回オープンの失敗として跳ね返ります。

非同期の書き込みストリーム(メインスレッド向け)

ワーカーを立てずメインスレッドで書きたい場合は、createWritable() が返す FileSystemWritableFileStream を使います。これは Streams APIWritableStream を継承した非同期ストリームです。

const fh = await root.getFileHandle("note.txt", { create: true });
const w = await fh.createWritable();
await w.write("hello");          // 末尾追記ではなく現在位置に書く
await w.write({ type: "seek", position: 0 });
await w.write("HELLO");
await w.close();                  // ここで初めて実ファイルへ反映される

重要なのは書き込みのアトミック性です。createWritable() は既定で**一時ファイル(スワップファイル)**に書き、close() の時点で元ファイルへ置き換えます。つまり close するまで元ファイルは変わらず、途中でクラッシュしても破損しません。逆に言えば、{ keepExistingData: false } の既定では一時ファイルが空から始まるため、部分更新したいときは keepExistingData: true を渡して既存内容を引き継ぐ必要があります。

I/O 経路実行コンテキスト同期性アトミック性
SyncAccessHandleワーカーのみ同期なし(直接書き込み・即時反映は flush 次第)
WritableFileStreamメイン/ワーカー両方非同期あり(close で置換)

用途で選びます。データベースエンジンのような高頻度ランダム I/O は SyncAccessHandle、ファイルを丸ごと書き出す保存処理は WritableStream が素直です。

ユーザー可視 FS の権限モデル

もう一方の世界、showOpenFilePicker などはユーザーの実ファイルを触るため、権限の扱いが厳密です。核になる概念は一時的活性化(transient activation)です。これらのピッカーは、クリックやキー入力といったユーザー操作の直後でなければ呼び出せません。load イベントや setTimeout から勝手に開くことはできず、SecurityError になります。

button.addEventListener("click", async () => {
  // ユーザー操作起点なので呼べる
  const [handle] = await window.showOpenFilePicker({ multiple: false });
  const file = await handle.getFile();     // 読み取りは許可済み
  const text = await file.text();

  // 書き戻すには writable 権限が要る
  const perm = await handle.queryPermission({ mode: "readwrite" });
  if (perm !== "granted") {
    await handle.requestPermission({ mode: "readwrite" }); // 再びプロンプト
  }
});

権限は granted / denied / prompt の3状態を取り、readreadwrite別々に管理されます。読み取り用に開いたハンドルへ書き戻すには、改めて requestPermission({ mode: "readwrite" }) を呼び、ユーザーの再許可が要ります。さらにこの権限はそのページのセッション内で有効なのが基本で、ページを再読み込みすると再許可が必要になります(ハンドル自体は 構造化複製可能 なので IndexedDB に保存して持ち越せますが、権限状態は別途 requestPermission で復活させます)。

触れない場所がある

ユーザー可視 FS では、ブラウザがシステム上重要なディレクトリ(OS のシステムフォルダ、ブラウザ本体のプロファイル領域など)への保存・選択をブロックします。ユーザーが選んだとしても拒否される場合があり、これは仕様上の安全策です。任意パスへ自由に書ける API ではない点を前提に設計してください。

OPFS とユーザー可視 FS の見分け方

ハンドルが同じ型でも、出どころで権限挙動が決まります。navigator.storage.getDirectory() 由来=OPFS(プロンプト無し・同期 I/O 可)。showOpenFilePicker 等の由来=ユーザー可視 FS(プロンプト必須・同期 I/O 不可)。queryPermission が常に granted を返すなら前者、と覚えると実装時に迷いません。

まとめ

File System API の要諦は、OPFS とユーザー可視 FS という別系統を1つの API 名前空間が束ねている、という構造の把握です。OPFS はオリジンに隔離されたサンドボックスで、権限プロンプト無しに使え、ワーカー上の createSyncAccessHandle で同期・低コピーの最速 I/O を提供します。これがブラウザ内 SQLite のような重量級データ層を可能にしています。一方ユーザー可視 FS は、一時的活性化を起点に read/readwrite を個別に許可する厳格なモデルで、実ファイルを安全に扱うための制約を伴います。どちらを使うかは「ブラウザ内で完結する高速ストレージが欲しいのか、ユーザーのファイルを編集したいのか」で決まり、その判断軸さえ持てば残りの API は素直に追えます。容量管理や退避の挙動は IndexedDB の内部 と共通なので、保存先全体の設計としてあわせて押さえてください。

Web/フロントエンド Article

Origin Private File SystemとFile System Access APIの内部を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

ブラウザ

比較で見る軸

難易度: advanced / カテゴリ: Web/フロントエンド / タグ数: 5

導入後に効く点

ワーカー上の createSyncAccessHandle は同期 read/write/flush を提供し、コピーや構造化複製を介さない最速の I/O 経路になる。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
Web/フロントエンド
タグ数
5

判断チェックリスト

  • 自社の用途が「ブラウザ / OPFS」に近いか確認する。
  • 強みである「OPFS はオリジンごとに隔離された不可視のファイルツリーで、ユーザーのファイルシステムとは別空間。ディレクトリ/ファイルハンドルでアクセスする。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

ブラウザOPFSFile System AccessストレージフロントエンドブラウザOPFSFile System Access
参考: 公式情報