Capabilities · API
registerProtocolHandler() and manifest protocol handlers
Published
Two routes register a web app as the handler for a URL scheme such as mailto: or web+music:. navigator.registerProtocolHandler(scheme, url) asks from a page, prompts the user, and works in an ordinary tab; the manifest protocol_handlers member registers with the operating system when the PWA is installed and needs no prompt from the page. Both run the HTML Standard’s “normalize protocol handler parameters” steps, so the scheme and URL rules are the same and a value that throws from the method is silently dropped from the manifest.
registerProtocolHandler() has been in Firefox since 2 and Chrome since 13 on desktop, with Edge 79 and Opera 11.6 following; Chrome 77 restricted url to http: and https:. Safari has no implementation, and no Android browser exposes it (BCD api.Navigator.registerProtocolHandler). unregisterProtocolHandler() exists in Chromium only, since Chrome 38. The manifest member is a desktop-only feature of Chrome 96 and Edge 96; see the compatibility table for the full position.
Syntax
Section titled “Syntax”navigator.registerProtocolHandler(scheme, url)navigator.unregisterProtocolHandler(scheme, url){ "protocol_handlers": [ { "protocol": "web+music", "url": "/play?song=%s" } ]}Both methods return undefined. Registration happens in parallel after the call returns, so there is no promise to await and no way to learn whether the user accepted the prompt. Both are [SecureContext], which the same-origin rule on url implies in any case. When a link with the registered scheme is activated, the browser percent-encodes the full link, substitutes it for %s, and navigates a new top-level browsing context to the result.
Parameters
Section titled “Parameters”The method arguments and the manifest entry members map one to one.
| Method argument | Manifest member | Type | Required | Description |
|---|---|---|---|---|
scheme |
protocol |
DOMString |
Yes | Lowercased before checking. Must be a safelisted scheme or start with web+ followed by one or more lowercase ASCII letters (web+music, not web+music2 or web+). A trailing colon makes it invalid. |
url |
url |
DOMString |
Yes | Must contain the literal %s. Parsed relative to the document (method) or the manifest URL (member); the result must be an http: or https: URL on the document’s origin, and for the manifest also within the manifest’s scope. |
The safelisted schemes are 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. Manifest processing skips an entry that lacks protocol or url, fails normalisation, falls outside scope, or repeats a url already registered by an earlier entry; it does not report why.
Exceptions
Section titled “Exceptions”registerProtocolHandler() and unregisterProtocolHandler() throw synchronously. The manifest member throws nothing; invalid entries are dropped during processing.
| Exception | Condition |
|---|---|
SecurityError |
scheme is neither safelisted nor web+ plus lowercase letters, including "mailto:" with its colon; the parsed url is not http: or https: or is not same-origin with the document; the browser blocks the registration, for example for http itself. Chrome also throws from Isolated Web Apps, directing authors to the manifest member. |
SyntaxError |
url does not contain %s; url fails to parse, which is forced when %s sits in the host or port. |
Examples
Section titled “Examples”The examples register web+music for a player at https://player.example/. The handler route is /play?song=%s, so a click on web+music:track/42 opens /play on that origin with a song parameter that URLSearchParams decodes back to web+music:track/42.
Offering registration from a settings page with a manual fallback
Section titled “Offering registration from a settings page with a manual fallback”Feature-detect the method and catch both exception names: a SecurityError here usually means a typo in the scheme, and the user may decline the prompt without the page ever knowing. Safari and every Android browser take the fallback branch, which shows the user how to paste a link instead.
const button = document.querySelector("#register-handler");
if (typeof navigator.registerProtocolHandler !== "function") { button.hidden = true; document.querySelector("#paste-link-help").hidden = false;} else { button.addEventListener("click", () => { try { navigator.registerProtocolHandler("web+music", "/play?song=%s"); button.textContent = "Requested; accept the browser prompt"; } catch (err) { if (err.name === "SecurityError" || err.name === "SyntaxError") { console.error(`${err.name}: ${err.message}`); } else { throw err; } } });}The HTML Standard tells browsers to remember declined registrations so the user is not prompted again, which means a repeated click after a refusal may show nothing; say so in the button’s help text.
Declaring the same handler in the manifest and reading the launched URL
Section titled “Declaring the same handler in the manifest and reading the launched URL”Installed users get the association without a prompt once the manifest carries the entry. The route receives the whole link in the song query parameter, scheme included, and has to strip the prefix itself.
{ "name": "Player", "start_url": "/", "scope": "/", "protocol_handlers": [ { "protocol": "web+music", "url": "/play?song=%s" } ]}const launched = new URL(location.href).searchParams.get("song");
if (launched && launched.startsWith("web+music:")) { const trackId = launched.slice("web+music:".length); // "track/42" loadTrack(trackId);} else { showLibrary(); // opened directly, not through a protocol link}Keep the route inside the manifest scope: an out-of-scope url is dropped from the processed manifest, and the install proceeds with no handler and no warning.
Removing a handler the user no longer wants
Section titled “Removing a handler the user no longer wants”unregisterProtocolHandler() takes the same two arguments and exists in Chromium only. Guard it separately from registerProtocolHandler(), because Firefox has the first method and not the second.
function forgetHandler() { if (typeof navigator.unregisterProtocolHandler !== "function") { return false; // Firefox: removal is only available from the browser's own settings } navigator.unregisterProtocolHandler("web+music", "/play?song=%s"); return true;}Unregistering through the method does not touch an association created by the manifest; that one is removed with the installed app.
See also
Section titled “See also”- Manifest protocol_handlers, the member’s own entry with its processing details
- Manifest file_handlers, the equivalent OS registration for file types
- Handle files
- HTML Standard: registerProtocolHandler() method (html.spec.whatwg.org)
- HTML Standard: safelisted schemes (html.spec.whatwg.org)
- Web App Manifest incubations: protocol_handlers member (wicg.github.io)
- URL protocol handler registration for PWAs (developer.chrome.com)
- Handle protocols in Progressive Web Apps (learn.microsoft.com)
Specifications
| Specification | Status |
|---|---|
| Manifest Protocol Handlers | W3C |
| HTML Standard: registerProtocolHandler() method | WHATWG living standard |
| HTML Standard: normalize protocol handler parameters | WHATWG living standard |
| Web App Manifest incubations: protocol_handlers member | WICG draft |
- 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).