10LOC
Chrome APIsadvanced

The File Handling API for browser file access

Published August 13, 2027

type LaunchParams = { files: FileSystemFileHandle[] };
type LaunchQueue = { setConsumer: (consumer: (params: LaunchParams) => void) => void };

export const handleLaunchedFiles = (onFiles: (files: File[]) => void): void => {
  const launchQueue = (window as { launchQueue?: LaunchQueue }).launchQueue;
  launchQueue?.setConsumer(async (params) => {
    if (!params.files.length) return;
    const files = await Promise.all(params.files.map((handle) => handle.getFile()));
    onFiles(files);
  });
};

What

window.launchQueue.setConsumer(callback) receives the files a user opened directly with your installed PWA — double-clicking a .csv in the OS file explorer, or right-clicking it and choosing your app — as FileSystemFileHandle objects, without the user going through your app's own file picker first.

Why it matters

Before the File Handling API, a PWA that wanted to act like a real desktop app for a given file type had no way to register as a handler for it at all — the only entry point for opening a file was always the app's own in-page <input type="file"> or showOpenFilePicker(), both of which require the user to already be inside the app. Native apps register file associations with the OS; web apps couldn't. This API closes that gap: once a PWA declares file_handlers in its manifest and gets installed, the OS treats it as a real handler for those file types — double-clicking a matching file in Explorer or Finder launches the app directly with that file, the same as it would a native editor.

How it works

  • The manifest declares the association (not shown in the snippet, since it's JSON, not TypeScript): "file_handlers": [{ "action": "/open-csv", "accept": { "text/csv": [".csv"] } }]action is the URL the app launches into, accept maps MIME types to the extensions that should trigger this handler.
  • LaunchQueue/LaunchParams are typed locally because TypeScript's DOM lib doesn't ship them (see Gotchas) — handleLaunchedFiles reads window.launchQueue through a narrow cast rather than assuming the ambient global exists.
  • setConsumer's callback fires with a LaunchParams object; params.files is an array of FileSystemFileHandle, not File — a handle you can re-read or write back to, not a one-shot snapshot, which is why handleFile.getFile() is called explicitly to get the File object with actual content.
  • Each handle's getFile() returns a fresh File on every call, which is why Promise.all maps over all of them before calling onFiles once with the full batch rather than firing per-file.

Gotchas

  • The app must be installed as a PWA for file_handlers to register at all — this does nothing for a site the user just has open in a browser tab.
  • Desktop only. There's no mobile equivalent; don't build a feature that depends on this without a fallback path for phones and tablets.
  • Chromium-only: Chrome and Edge since version 102 — no Firefox or Safari support, and no public signal either intends to add it.
  • The first time a launch happens after install, the user gets a one-time permission prompt to confirm the app should handle that file type; changing file_handlers in the manifest resets that permission, so users get re-prompted after an update that adds or changes handlers.
  • launchQueue only exists in a launched-with-files context — checking "launchQueue" in window isn't enough on its own; also check "files" in LaunchParams.prototype (this snippet's optional-chaining on setConsumer sidesteps that by simply doing nothing if the global isn't there).

Related

  • File System Access API (showOpenFilePicker, FileSystemFileHandle) — the general-purpose file API this builds on; File Handling is specifically about the OS launching your app with a file already selected.
  • Web Share Target API — the equivalent pattern for receiving shared content (not files opened from disk) from the OS share sheet.