# Manifest protocol_handlers

> The protocol_handlers manifest member registers a web app in the OS's application preferences as a handler for URL schemes such as mailto or web+jngl.

import CompatTable from '@components/CompatTable.astro';

Per MDN, the `protocol_handlers` member "specifies an array of objects that are protocols
which this web app can register and handle," and "protocol handlers register the
application in an OS's application preferences; the registration associates a specific
application with the given protocol scheme." MDN's own illustration: "when using the
protocol handler `mailto://` on a web page, registered email applications open."

## Where it is supported

<CompatTable feature="protocol-handlers" />

MDN marks the member **experimental**. Its browser-compat-data entry for
`manifests.webapp.protocol_handlers` records support in Chrome 96, with Edge and Opera
listed as mirroring Chrome's data. Firefox and Safari are recorded with no support, as is
Chrome on Android; the remaining mobile entries (Firefox for Android, Opera Android,
Safari on iOS, Samsung Internet, and the Android and iOS WebViews) are recorded as
mirrors of those same engines.

## How to use it

Each entry pairs a `protocol` with a `url`. Per MDN, `protocol` is "a required string
containing the protocol to be handled; e.g.: `mailto`, `ms-word`, `web+jngl`," and `url`
is a "required HTTPS URL within the application `scope` that will handle the protocol,"
where "the `%s` token will be replaced by the URL starting with the protocol handler's
scheme." MDN adds that "if `url` is a relative URL, the base URL will be the URL of the
manifest."

MDN's example declares that the app "should be registered to handle the protocols
`web+jngl` and `web+jnglstore`":

```json
{
  "protocol_handlers": [
    {
      "protocol": "web+jngl",
      "url": "/lookup?type=%s"
    },
    {
      "protocol": "web+jnglstore",
      "url": "/shop?for=%s"
    }
  ]
}
```

MDN describes what happens next: "after registering a web app as a protocol handler, when
a user clicks on a hyperlink with a specific scheme such as `mailto://` or `web+music://`
from a browser or native app, the registered PWA would open and receive the URL."

The Manifest Incubations specification splits that into two actors. On installation, "a
user agent SHOULD register protocol handlers with the Operating System," and from then on
"clicking on a registered protocol will launch the registered application. If this is the
web app, then execute the [invoke a protocol handler] steps defined in [HTML], where the
user agent SHOULD navigate to url and the appropriate browsing context is set to a new top
level browsing context." Where several apps claim the same protocol, the spec expects that
"the OS allows the user to select which app should open it, and also allows the user to set
a default app."

The spec's own worked example makes the substitution concrete: for a manifest at
`https://example.com/manifest.webmanifest`, a `web+music` handler with
`"url": "/play?songId=%s"` activated on `web+music://#1234` means "the user agent would
instantiate a new top-level browsing context and navigate to
`https://example.com/play?songId=web+music://%231234`."

## How to detect the substituted URL and fall back

Per MDN, `%s` is replaced with "the URL starting with the protocol handler's scheme."
The cited sources do not define the unsubstituted template as a fallback URL, so the route
should accept only the documented substituted shape and use its fallback branch for an
absent or invalid value:

```js
// The route the manifest points at: /lookup?type=%s
//
// Note the parser. MDN: "The URLSearchParams constructor interprets plus signs
// (+) as spaces, which might cause problems." In the spec's worked example the
// "+" of "web+music" is literal in the query string, so read the raw value.
const encoded = location.search.match(/[?&]type=([^&]*)/)?.[1];
let value = null;

try {
  value = encoded ? decodeURIComponent(encoded) : null;
} catch {
  // An unsubstituted or malformed value is not a handled protocol URL.
}

if (value?.startsWith('web+jngl:')) {
  // The value has the shape MDN documents for a substituted %s.
  showLookupFor(value.slice('web+jngl:'.length));
} else {
  // Absent, or not a web+jngl URL. Render the normal lookup page with its own
  // search field instead of parsing a value that isn't there.
  showLookupForm();
}
```

## Practical checklist

- [ ] Keep the handler `url` inside the app's `scope` and on HTTPS, per MDN. The spec's
      processing steps skip any entry whose normalized URL "is not within scope of"
      the manifest, and also skip entries where `protocol` or `url` is undefined.
- [ ] Do not expect an arbitrary scheme to be accepted. In the spec's example, the second
      handler "would be ignored, as the protocol provided does not start with `web+` and is
      not part of the safelisted schemes."
- [ ] Do not register the same URL twice: per the spec's processing steps, if the processed
      list already contains the normalized URL, the entry is skipped.
- [ ] A permission prompt is not guaranteed, and the presented list may be shortened. Per
      the spec, "a user agent SHOULD ask users for permission before registering a protocol
      handler description `protocol_handlers` as the default handler for a protocol with
      the host operating system," and it "MAY truncate the list ... in order to remain
      consistent with the conventions or limitations of the host operating system."
- [ ] Treat registration as operating-system dependent, and not only an install-time act.
      Per MDN, "registering applications to handle URL schemes is operating-system
      dependent. This association is usually done during application install but it can also
      be done afterwards from an app that has already been installed."
- [ ] Parse the whole URL, not a payload. `%s` is replaced "by the URL starting with the
      protocol handler's scheme" (MDN) — in the spec's example the query value is the entire
      `web+music://#1234`, with the fragment marker written as `%23`.
- [ ] Be aware the registration can become a default silently. The spec's privacy note warns
      that "depending on the operating system capabilities, the protocol handler could become
      a 'default' handler ... of a given protocol without the explicit knowledge of the
      user," and lists the protections a user agent may employ, including removing the
      registration.

## Where to go next

- [Manifest protocol_handlers: registering a PWA for custom URL schemes](/reference/capabilities/protocol-handlers/)
  — the capability-side entry, including the older imperative registration API.
- [Manifest Protocol Handlers support](/compatibility/protocol-handlers/) — the
  compatibility dataset for this member.
- [Manifest scope](/reference/manifest/scope/) — the member that bounds which handler URLs
  the spec will accept.