How to Handle Files Synchronously in the Browser

Mar 20, 2026

4 min
💬

Three concepts you need to know when you want synchronous file I/O in the browser — OPFS, Web Worker, and FileSystemSyncAccessHandle, each explained from its own angle.

1. Overview

To do synchronous file I/O in the browser, you have to solve three problems at once: where to store, where to execute, and how to do the I/O. OPFS provides a browser-internal private storage area, Web Worker lets you run blocking work off the main thread, and FileSystemSyncAccessHandle lets you read and write OPFS files synchronously. All three concepts show up together in Pyodide's browser file I/O issue as well.

Here are the key points to check.

  • Why OPFS is needed and how it differs from the existing approaches
  • How Web Worker relates to file I/O
  • What problem FileSystemSyncAccessHandle solves
  • Why these three concepts always appear together

2. Why OPFS Is Needed

For a long time, file handling in the browser relied on limited APIs based on <input type="file"> or FileReader. The File API is more about reading and processing files that the user provides through a file input or drag and drop.

Later the File System Access API arrived, letting you read and write local files with user consent, but working with the user-visible file system and an origin-private storage area serve different purposes. OPFS is a private storage endpoint per origin — closer to an app-only storage area managed by the browser.

OPFS was created to solve this problem. OPFS (Origin Private File System) is a browser-internal file system isolated per origin (website address). Since it's a storage area never directly exposed to the user, its permission and security flow is simpler than the user-visible file system, and combined with the synchronous access handle inside a Worker, it fits nicely with the file I/O model of WASM-based apps.

  • There is a capacity limit — like IndexedDB, it follows the browser storage quota, and you can check usage with navigator.storage.estimate().
  • It gets deleted when site data is cleared.
  • The user cannot find the file paths.
  • It offers a synchronous API — every other method is async, but OPFS alone supports synchronous access via FileSystemSyncAccessHandle. The catch: it works only inside a Dedicated Web Worker.

NOTE

OPFS is a browser-internal file system isolated per origin. With no security checks or permission prompts, it can be accessed without async overhead, and inside a Dedicated Web Worker it even supports synchronous file access.

3. The Three Concepts

OPFS, Web Worker, and FileSystemSyncAccessHandle are concepts from three different angles.

3.1. OPFS — Where Do You Store

OPFS is a sandboxed file system managed by the browser. Each origin gets its own independent storage space, invisible in the file explorer. There are two ways in — the async API, available on both the main thread and Workers, and FileSystemSyncAccessHandle, available only inside a Worker.

JavaScript
// async API로 OPFS 접근 (메인 스레드에서도 가능)
const root = await navigator.storage.getDirectory();
const fileHandle = await root.getFileHandle('data.bin', { create: true });
 
// 쓰기
const writable = await fileHandle.createWritable();
await writable.write('hello');
await writable.close();
 
// 읽기
const file = await fileHandle.getFile();
const text = await file.text();

TIP

OPFS is the "where do you store" question. It is a browser-internal file system isolated per origin, accessed in two ways: the async API (main thread / Worker) and FileSystemSyncAccessHandle (Worker only).

3.2. Web Worker — Who Executes

JavaScript is single-threaded by default. All code runs on the main thread, which also handles UI rendering and event processing, so running heavy computation or blocking I/O on the main thread freezes the screen. A Web Worker creates a separate thread apart from the main thread, and instead of touching the DOM directly, it communicates through postMessage()-based messages.

JavaScript
// 메인 스레드
const worker = new Worker('worker.js');
worker.postMessage({ type: 'start' });
worker.onmessage = (e) => console.log('결과:', e.data);
 
// worker.js
self.onmessage = async (e) => {
  // 여기서 블로킹 작업 수행
  self.postMessage({ result: '완료' });
};

TIP

The Web Worker is the "who executes" question. To keep the main thread from blocking, synchronous I/O is only allowed inside a Worker.

3.3. FileSystemSyncAccessHandle — How Do You Read and Write

FileSystemSyncAccessHandle is one of the ways to access OPFS files, and as the name says, it reads and writes synchronously (Sync). Methods like read, write, getSize, flush, truncate, and close operate synchronously. The constraints: this handle can only be created on OPFS files, and it is only accessible inside a Dedicated Web Worker.

JavaScript
// Worker 안에서
const root = await navigator.storage.getDirectory();
const fileHandle = await root.getFileHandle('fast.bin', { create: true });
 
// 핸들 생성은 async (한 번만)
const accessHandle = await fileHandle.createSyncAccessHandle();
 
// 이 아래는 전부 동기 — await 없음
const encoder = new TextEncoder();
const buf = encoder.encode('Some data');
 
accessHandle.write(buf, { at: 0 });  // 동기 쓰기
accessHandle.flush();                // 디스크에 반영
const size = accessHandle.getSize(); // 동기 크기 조회
const view = new DataView(new ArrayBuffer(size));
accessHandle.read(view, { at: 0 });  // 동기 읽기
accessHandle.close();

Since read()/write() are blocking calls that could freeze the main thread, the spec allows them only inside a Dedicated Web Worker. The createSyncAccessHandle() method itself is an async call that returns a Promise, but the file operation methods on the returned handle are synchronous.

WARNING

Per the spec, FileSystemSyncAccessHandle can only be used inside a Dedicated Web Worker. Calling createSyncAccessHandle() on the main thread throws an error.

4. How the Three Concepts Interlock

Each solves a different problem, but for the goal of "implementing synchronous file I/O in the browser," you need all three at once.

Problem to solveResponsible concept
Persist files fast and without permissionsOPFS
Implement synchronous read / writeFileSystemSyncAccessHandle
Keep blocking calls from freezing the UIWeb Worker

Not one of them can be missing.

  • File System Access API instead of OPFS → you can access the user's local files, but security checks make it slow and a permission popup appears every time. (Different purposes: File System Access API = user files, OPFS = browser-internal only)
  • async only, without FileSystemSyncAccessHandle → the synchronous f.read() pattern is impossible
  • Main thread instead of a Worker → blocking calls freeze the UI

Sync I/O execution flow diagram

TIP

OPFS is the storage location, the Web Worker is the execution context, and FileSystemSyncAccessHandle is the I/O method. They are different perspectives, each solving a different problem.

5. Comparing OPFS Access APIs

APISync?OPFS onlyRead/writeRecommended context
getFile()asyncRead onlyMain thread / Worker
createWritable()asyncWrite onlyMain thread / Worker
createSyncAccessHandle()sync✅ OPFS onlyRead + writeWorker recommended

6. Wrapping Up

When I first read about Pyodide's browser file I/O context, the three concepts felt lumped together as one, but separating them by perspective makes everything clear.

  • OPFS — where do you store (storage location)
  • Web Worker — who executes (execution context)
  • FileSystemSyncAccessHandle — how do you read and write (I/O method)

TIP

Each of the three concepts solves its own problem, and together they interlock toward the single goal of "synchronous file I/O in the browser."

7. References