Skip to content

Manifest · Manifest member

protocol_handlers manifest member

Published Updated

Limited availabilityNot supported in Chrome (Android), Safari (iOS), Safari (macOS), Firefox (Desktop)W3C

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.

  • Type: array of objects. Each object has two required strings: protocol, a scheme name without the colon, and url, an HTTPS URL inside the manifest scope that contains the literal token %s. A relative url resolves 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.

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.

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.

Specifications

SpecificationStatus
Manifest Protocol HandlersW3C
Manifest Incubations: protocol_handlers memberWICG draft
HTML Standard: safelisted schemesWHATWG living standard
  • Legend
  • Yes
  • Partial
  • Flag
  • No
  • Unknown
Browser / PlatformSupportVersionsConfidenceSourceNotes
Chrome (Android)No—mediumsource1
Chrome (Desktop)Yes96mediumsource—
Edge (Desktop)Yes96mediumsource—
Safari (iOS)No—mediumsource2
Safari (macOS)No—mediumsource3
Firefox (Desktop)No—mediumsource4
Samsung InternetNo—mediumsource5
  1. Desktop-only registration.
  2. Safari does not implement the `protocol_handlers` manifest member (MDN compatibility table, checked 2026-10-03).
  3. Safari does not implement the `protocol_handlers` manifest member (MDN compatibility table, checked 2026-10-03).
  4. Desktop Firefox does not install web apps from the manifest and does not implement `protocol_handlers` (MDN compatibility table, checked 2026-10-03).
  5. Chromium ships manifest protocol handlers for desktop installs only; Samsung Internet lists no support (MDN compatibility table, checked 2026-10-03).

Source data: /compatibility/protocol-handlers.json · Global usage: 37 % (StatCounter 2026-05)

Source: spec · MDN · Last verified 2026-06-24 · Confidence: medium (computed from sources)