Manifest · Manifest member
protocol_handlers manifest member
Published Updated
protocol_handlers is an array of { protocol, url } objects that asks the browser to register
the installed web app with the operating system as a handler for URL schemes. Once registered,
a web+note:// or mailto: link clicked in any application opens the web app at the url,
with %s replaced by the full link.
Chrome 96 and Edge 96 implement the member for desktop installs on Windows, macOS, and Linux
(BCD html.manifest.protocol_handlers). Chrome on Android, Samsung Internet, Firefox 157, and
Safari 27 do not read it. The runtime API navigator.registerProtocolHandler() covers a
different case: it registers the current site, in the browser only, and needs a user gesture
on each visit; the manifest member registers at install time and reaches the OS.
Member
Section titled “Member”- Type: array of objects. Each object has two required strings:
protocol, a scheme name without the colon, andurl, an HTTPS URL inside the manifestscopethat contains the literal token%s. A relativeurlresolves against the manifest URL. - Default: an empty array; nothing is registered.
- Example value:
[{ "protocol": "web+note", "url": "/open?note=%s" }].
protocol must be either a custom scheme beginning with web+ followed by lowercase ASCII
letters, or one of the schemes the HTML Standard safelists: bitcoin, ftp, ftps, geo,
im, irc, ircs, magnet, mailto, matrix, mms, news, nntp, openpgp4fpr, sftp,
sip, sms, smsto, ssh, tel, urn, webcal, wtai, and xmpp. Anything else, such
as note or ms-word, is skipped.
Chromium’s parser drops an entry rather than the array when it is malformed:
protocol_handlers entry ignored, required property 'protocol' is invalid. and
protocol_handlers entry ignored, required property 'url' is invalid. name the missing field,
and a url without the token produces The url provided ('/open?note=') does not contain '%s'. The spec also skips an entry whose url falls outside scope and a second entry whose
normalized url duplicates an earlier one.
Registration is per install: Chrome writes the handler into the OS when the app is installed and removes it on uninstall. The first time a link with the scheme launches the app, Chrome shows a confirmation dialog naming the app before navigating, and the OS may ask the user to pick a default when more than one application claims the scheme.
Examples
Section titled “Examples”The manifest side is a few lines; the handler page has to cope with a value that may be absent, encoded, or not the scheme it expected.
Registering a custom web+ scheme for a notes app
Section titled “Registering a custom web+ scheme for a notes app”A notes app claims web+note so that web+note://2026-10-03 links in email and chat open the
matching entry. The url sits under the app’s scope and carries %s as a query value, which
keeps the rest of the path static.
{ "name": "Notebook", "start_url": "/", "scope": "/", "display": "standalone", "protocol_handlers": [ { "protocol": "web+note", "url": "/open?note=%s" } ]}Only one handler per scheme is registered; listing the same protocol twice with different
URLs keeps the first entry.
Decoding the substituted link on the handler page
Section titled “Decoding the substituted link on the handler page”The browser replaces %s with the entire link, so the page receives web+note://2026-10-03,
not 2026-10-03, with characters such as # percent-encoded. Read the raw query value and
decode it yourself: URLSearchParams turns + into a space and would corrupt web+note. The fallback branch
renders the ordinary page when the value is missing or carries another scheme, which is what a
direct visit to /open looks like.
const raw = location.search.match(/[?&]note=([^&]*)/)?.[1];let link = null;try { link = raw ? decodeURIComponent(raw) : null;} catch { link = null; // malformed percent-encoding: treat as no link}
if (link?.startsWith('web+note://')) { openNote(link.slice('web+note://'.length)); // "2026-10-03"} else { showNoteList(); // direct visit, or a scheme this page does not handle}Replace the query with history.replaceState() after reading it so a reload or a bookmark does
not re-run the handler.
Falling back to the runtime API where the manifest is ignored
Section titled “Falling back to the runtime API where the manifest is ignored”On browsers without the manifest member, navigator.registerProtocolHandler() registers the
same scheme inside the browser only, and must be called from a user gesture. Feature-detect
both and expose a button for the runtime path.
export function offerProtocolRegistration(button) { const installed = matchMedia('(display-mode: standalone)').matches; if (installed) return; // Chrome/Edge desktop: the manifest already registered web+note if (typeof navigator.registerProtocolHandler !== 'function') return; // Safari: no path button.hidden = false; button.addEventListener('click', () => { navigator.registerProtocolHandler('web+note', `${location.origin}/open?note=%s`); });}Firefox 157 accepts the runtime registration for web+ schemes; the handler then applies to
links clicked inside Firefox, not to links in other applications.
See also
Section titled “See also”- Manifest protocol_handlers: registering a PWA for custom URL schemes
- scope manifest member
- launch_handler manifest member
- Manifest Incubations: protocol_handlers member (wicg.github.io)
- Handle protocols in PWAs (learn.microsoft.com)
- Navigator: registerProtocolHandler() method (developer.mozilla.org)
Specifications
| Specification | Status |
|---|---|
| Manifest Protocol Handlers | W3C |
| Manifest Incubations: protocol_handlers member | WICG draft |
| HTML Standard: safelisted schemes | WHATWG living standard |
- Legend
- Yes
- Partial
- Flag
- No
- Unknown
| Browser / Platform | Support | Versions | Confidence | Source | Notes |
|---|---|---|---|---|---|
| Chrome (Android) | No | — | medium | source | 1 |
| Chrome (Desktop) | Yes | 96 | medium | source | — |
| Edge (Desktop) | Yes | 96 | medium | source | — |
| Safari (iOS) | No | — | medium | source | 2 |
| Safari (macOS) | No | — | medium | source | 3 |
| Firefox (Desktop) | No | — | medium | source | 4 |
| Samsung Internet | No | — | medium | source | 5 |
- Desktop-only registration.
- Safari does not implement the `protocol_handlers` manifest member (MDN compatibility table, checked 2026-10-03).
- Safari does not implement the `protocol_handlers` manifest member (MDN compatibility table, checked 2026-10-03).
- Desktop Firefox does not install web apps from the manifest and does not implement `protocol_handlers` (MDN compatibility table, checked 2026-10-03).
- Chromium ships manifest protocol handlers for desktop installs only; Samsung Internet lists no support (MDN compatibility table, checked 2026-10-03).