· deepdives  · 7 min read

Beyond Uploads: How the File System Access API is Changing Web Development

Explore how the File System Access API lets web apps read, write and manage local files and folders - without the upload roundtrip. Learn how to use it, see code examples, examine real-world case studies and discover best practices and limitations.

Explore how the File System Access API lets web apps read, write and manage local files and folders - without the upload roundtrip. Learn how to use it, see code examples, examine real-world case studies and discover best practices and limitations.

Outcome first: with the File System Access API you can open entire projects from the user’s disk, edit huge media files in the browser without uploading them to a server, and persist changes back to the exact files the user expects - all with native-like performance and security.

This article shows how. You’ll get practical examples, real-world case studies, and guidance for shipping production-ready experiences.

Why this matters - the short answer

Traditional web apps treat the user’s local files as transient blobs: you select a file, it uploads to a server, the server stores it, and the browser loses direct access. Slow. Privacy-leaking. Unnecessary.

The File System Access API changes that. It gives web apps a secure, permissioned bridge to local files and directories so you can read, stream, edit, and write files in place. No opaque uploads required for many workflows.

What the API provides (quick map)

  • showOpenFilePicker / showSaveFilePicker / showDirectoryPicker - user-driven pickers that return FileSystemHandles.
  • FileSystemFileHandle & FileSystemDirectoryHandle - handles representing files and folders.
  • FileSystemWritableFileStream - write data back to disk (createWritable, write, truncate, close).
  • Permission model - requestPermission and queryPermission; handles can be persisted (e.g., in IndexedDB) if the user granted access.
  • Streams and chunked access - process huge files with minimal memory by streaming.

Learn more: the MDN overview is a solid reference: https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API and there’s a deep practical guide at https://web.dev/file-system-access/.

Basic examples - open, read, write (copy-paste friendly)

Open a single file and read its contents:

if ('showOpenFilePicker' in window) {
  const [fileHandle] = await window.showOpenFilePicker();
  const file = await fileHandle.getFile();
  const text = await file.text();
  console.log(text);
} else {
  // Fallback: <input type="file">
}

Save a string into a new file (save-as flow):

const handle = await window.showSaveFilePicker({
  suggestedName: 'notes.txt',
  types: [{ description: 'Text Files', accept: { 'text/plain': ['.txt'] } }],
});
const writable = await handle.createWritable();
await writable.write('Hello from the web!');
await writable.close();

Open a directory and iterate files (useful for project editors):

const dirHandle = await window.showDirectoryPicker();
for await (const [name, handle] of dirHandle.entries()) {
  if (handle.kind === 'file') {
    const file = await handle.getFile();
    console.log(name, file.size);
  }
}

Stream a large file and process chunks without loading everything into memory:

const [fileHandle] = await window.showOpenFilePicker();
const file = await fileHandle.getFile();
const stream = file.stream();
const reader = stream.getReader();
let received = 0;
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  received += value.length;
  // process chunk (value is a Uint8Array)
}
console.log('Processed bytes:', received);

Permission and persistence - how to retain access

Handles are permissioned. Users explicitly pick files/folders. You can store handles in IndexedDB to reuse them later - but only if the user granted persistent permission. Always test queryPermission() and requestPermission() before accessing a handle.

Example pattern:

// save handle to IndexedDB (pseudo)
await idb.put('handles', fileHandle, 'project-root');

// later
const handle = await idb.get('handles', 'project-root');
const perm = await handle.queryPermission({ mode: 'readwrite' });
if (perm !== 'granted') {
  const r = await handle.requestPermission({ mode: 'readwrite' });
  if (r !== 'granted') throw new Error('User denied permission');
}

Note: the user can revoke permissions at any time, and browsers may impose lifetime limits. Always code defensively.

Case study 1 - A web IDE that feels native

Problem: developers want to edit local projects in the browser but hate uploading projects or working with virtual filesystems.

What the API enables:

  • Open a project folder with showDirectoryPicker.
  • Read & write files directly via FileSystemFileHandle.
  • Persist directory handles in IndexedDB so the user returns to the same workspace.
  • Perform file operations (rename, create, delete) on the user’s disk without intermediate uploads.

Real-world impact:

  • Faster edit/save cycles - instantly write to the disk.
  • Better UX: source control clients can operate on real files instead of sync layers.
  • Lower infrastructure costs - fewer server-hosted file stores.

Notes: editors still need to handle permission revocation and fallback for unsupported browsers.

Case study 2 - Large-media editing with streaming and WASM

Problem: video/audio/photo editing apps need to process multi-hundred-megabyte assets, often uploading them to servers for processing.

What the API enables:

  • Open a video file with showOpenFilePicker and stream bytes into a WASM encoder (e.g., FFmpeg compiled to WASM).
  • Perform edits on chunks and write results back using createWritable() to avoid copying the whole file in memory.
  • Export final assets directly to a chosen folder with showSaveFilePicker or by creating files inside a directory handle.

Why this matters:

  • You keep large files on disk. No server transfer.
  • Processing can be offline and private.
  • Performance improvements when using streaming and chunked writes.

Example: a photo editor can open a raw file, apply transforms using WASM image libs, and write incremental changes to a sidecar file (or overwrite the original) with atomic write patterns using createWritable().

Case study 3 - Desktop-like document management and sync

Problem: teams want versioned documents stored locally but synchronized with cloud systems and searchable offline.

What the API enables:

  • Present documents from local folders inside a web app UI.
  • Let users edit directly and persist changes to disk.
  • Use file handles plus a lightweight sync engine to push diffs to cloud storage when online.

Implementation pattern:

  • Track file modification timestamps and content hashes.
  • On change, write to disk immediately with createWritable().
  • Queue sync jobs that read the changed ranges (via read streams) and upload deltas, reducing bandwidth.

This pattern yields a hybrid model: local-first editing for speed and privacy, with optional cloud sync for collaboration.

Practical patterns and best practices

  1. Feature-detect and progressive-enhance

Always test for the API and provide a fallback file input or server-proxied upload. Example:

if (!('showOpenFilePicker' in window)) {
  // fallback UI
}
  1. Treat handles as user-granted capabilities

Explain clearly what the app will do with files. Provide granular prompts and allow the user to revoke or re-select a folder.

  1. Use Writable streams for atomic writes

Write to a temp file first or use WritableFile.createWritable({ keepExistingData: false }) patterns (browser support permitting), then rename/replace to avoid corrupting originals.

  1. Minimize memory usage with streaming

Large files must be handled as streams; avoid reading the entire file into memory.

  1. Store handles thoughtfully

If you persist handles in IndexedDB, provide an explicit UI to manage stored projects and an easy way to re-request permissions.

  1. Respect the permission model and user agency

Always call queryPermission and requestPermission. Fail gracefully when access is removed.

Limitations and cross-browser considerations

  • Browser support is evolving. Chromium-based browsers (Chrome, Edge) and many recent Android browsers have solid support. Other browsers may lag or implement partial support. Check current compatibility: https://caniuse.com/?search=file%20system%20access
  • File system access requires a user gesture (pickers must be user-initiated). You cannot silently access the disk from the background.
  • Mobile support is improving but still more constrained than desktop.
  • There are security sandboxing and privacy constraints: you cannot enumerate the user’s entire disk; picks are explicit and scoped to chosen directories.

For up-to-date specs and notes, see the WICG spec: https://w3c.github.io/file-system-access/ and the explainer at https://web.dev/file-system-access/.

  • Design for intermittent access: always re-check permissions and gracefully fallback.
  • Keep a thin sync layer: use local-first edits, compute deltas, and sync only changed ranges.
  • Offer an onboarding flow: show why you need folder access and what you will (and won’t) do with files.
  • Provide an explicit “reveal in folder” or “open local file” action so the user understands where data lives.

Security and privacy considerations

  • Users expect clarity. Show clear affordances (what file/folder was selected, what changes the app will make).
  • Avoid storing sensitive handles without user consent.
  • Verify uploaded content server-side if you later sync to remote servers.

The future and integration opportunities

  • PWAs + File System Access can blur the line between web and desktop apps. Installable apps will be able to open and persist projects just like native apps.
  • More standardization and cross-browser support will unlock larger adoption.
  • Integration with WebAssembly, WebCodecs, and WebTransport creates powerful combos: local editing + fast codecs + network-efficient sync.

Conclusion - what you can build next

The File System Access API doesn’t just simplify uploads. It lets you build web applications that work with the user’s real files and folders - securely, performantly, and privately. From fully featured browser IDEs to offline-first media editors and hybrid sync tools, this API unlocks experiences previously reserved for native apps.

Start small: add a folder picker and implement one read/write flow. Then iterate. The web is finally able to treat local files as first-class citizens.

For implementation details and live examples, see the official guides:

The File System Access API brings the local file system to the web - and that changes everything.

Back to Blog

Related Posts

View All Posts »