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"] } }]—actionis the URL the app launches into,acceptmaps MIME types to the extensions that should trigger this handler. LaunchQueue/LaunchParamsare typed locally because TypeScript's DOM lib doesn't ship them (see Gotchas) —handleLaunchedFilesreadswindow.launchQueuethrough a narrow cast rather than assuming the ambient global exists.setConsumer's callback fires with aLaunchParamsobject;params.filesis an array ofFileSystemFileHandle, notFile— a handle you can re-read or write back to, not a one-shot snapshot, which is whyhandleFile.getFile()is called explicitly to get theFileobject with actual content.- Each handle's
getFile()returns a freshFileon every call, which is whyPromise.allmaps over all of them before callingonFilesonce with the full batch rather than firing per-file.
Gotchas
- The app must be installed as a PWA for
file_handlersto 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_handlersin the manifest resets that permission, so users get re-prompted after an update that adds or changes handlers. launchQueueonly exists in a launched-with-files context — checking"launchQueue" in windowisn't enough on its own; also check"files" in LaunchParams.prototype(this snippet's optional-chaining onsetConsumersidesteps 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.