File Handling Protocol Handlers
File Handling lets your PWA register as the default opener for custom file types -- double-click a .recipe and your app launches with it. Protocol Handlers do the same for custom URL schemes like web+myapp://
When a user double-clicks a .psd file in Windows Explorer, Photoshop opens. When they click a mailto: link, their email app opens.
Both of those behaviors -- file association and protocol registration -- are now available to PWAs. Your web app can be the handler.
File Handling API
Two steps: declare what file types you handle in the manifest, then consume the files in JavaScript.
Step 1: Declare in the Manifest
{
"file_handlers": [
{
"action": "/open",
"accept": {
"application/x-recipe": [".recipe"],
"text/plain": [".txt"]
}
}
]
}action is the URL path the browser navigates to when launching the PWA with a file. accept maps MIME types to file extensions -- the same format as an <input type="file" accept=""> attribute.
When the user double-clicks a .recipe file on their desktop, the OS launches your PWA and navigates to /open. The file is not in the URL. It arrives through a queue.
Step 2: Consume the Launch Queue
window.launchQueue.setConsumer(async (launchParams) => {
for (const handle of launchParams.files) {
const file = await handle.getFile()
const text = await file.text()
console.log('Opened file:', file.name)
console.log('Contents:', text)
}
})window.launchQueue is the channel between the OS file manager and your app. launchParams.files is an array of FileSystemFileHandle objects -- the same type returned by the File System Access API. Call .getFile() to get a File object, then read it with .text(), .arrayBuffer(), or .stream().
The consumer fires once per launch. If the user opens multiple files at once, all handles arrive in a single launchParams.files array.
Availability: Chromium-based browsers on desktop, installed PWA only.
if ('launchQueue' in window) {
// File Handling API is available
}Protocol Handlers
Protocol handlers let your PWA respond to custom URL schemes. When any link in the OS -- inside another app, in an email, in a document -- uses your protocol, your PWA opens it.
Standard Protocols via registerProtocolHandler
For standard schemes like mailto and tel, use the JavaScript API:
navigator.registerProtocolHandler('mailto', 'https://mymail.example/compose?to=%s')When the user clicks a mailto:someone@example.com link anywhere on the OS, your web app opens at https://mymail.example/compose?to=mailto%3Asomeone%40example.com. The %s is replaced with the full original URL.
Custom Protocols via the Manifest
For your own protocol, declare it in the manifest. Custom protocols must have a web+ prefix -- that is a spec requirement, not a convention:
{
"protocol_handlers": [
{
"protocol": "web+myrecipeapp",
"url": "/handle?recipe=%s"
}
]
}When someone clicks a link like web+myrecipeapp://bolognese-recipe, the OS launches your PWA and navigates to /handle?recipe=web%2Bmyrecipeapp%3A%2F%2Fbolognese-recipe.
The %s receives the full protocol URL. You extract what you need:
const params = new URLSearchParams(location.search)
const recipeUrl = new URL(decodeURIComponent(params.get('recipe')))
const recipeName = recipeUrl.pathname.slice(1) // "bolognese-recipe" ExpandFile Handling and Protocol Handlers: OS launch flow to launchQueue, and web+ protocol routing to app URL
PWA vs Electron
This kind of OS integration is where the question "why not just use Electron?" comes up.
Electron bundles a full Chromium engine with your app. Even a Hello World is ~50MB. The bundled engine doesn't receive browser updates -- if you don't recompile, users keep running whatever Chromium version you shipped with.
A PWA gets no bundled engine. It runs in whatever browser the user already has -- which means it automatically gets browser security updates and performance improvements. Deployments are silent: update the files on the server, users get the new version on next launch.
The tradeoff is capability. Electron can run native code, access OS APIs that browsers haven't exposed, and ship to the Mac App Store with full sandboxed access. If you need something the browser hasn't implemented yet, Electron (or its mobile equivalent, Capacitor/Cordova) is the path.
The practical rule: if the capability exists as a browser API, prefer a PWA. If it does not, you need a native wrapper.
Support
File Handling and Protocol Handlers are both Chromium-only, desktop-only, and require an installed PWA. Both are defined in the manifest -- no install, no effect.
The next OS integration is about giving content to other apps rather than receiving it. Web Share triggers the native OS share sheet from a single function call.
The Essentials
- File Handling: declare
file_handlersin manifest withactionURL andacceptMIME/extension pairs. Read files viawindow.launchQueue.setConsumer()-- receivesFileSystemFileHandleobjects. - Protocol Handlers:
navigator.registerProtocolHandler()for standard schemes (mailto,tel). Manifestprotocol_handlersfor custom schemes -- custom protocols must have theweb+prefix. - Both deliver data via URL replacement (
%sin the URL pattern) or the launch queue -- not via URL query params directly. - Both are Chromium-only, desktop-only, and require an installed PWA.
- PWA vs Electron tradeoff: PWA is zero-size, auto-updated, browser-sandboxed. Electron can access any OS API but ships 50MB+ and requires manual update management.
Further Reading and Watching
- Handle files from your Progressive Web App (YouTube) -- Patrick Brosset demonstrates the File Handling API in action, showing the manifest setup, launchQueue consumer, and a real demo of a PWA opening a custom file type from the OS file manager
- Let installed web applications be file handlers -- Chrome for Developers -- official guide covering the manifest declaration, LaunchQueue API, and the
window.launchQueueconsumer pattern
Keep reading