Skip to main content
Avoid maintaining separate manifest files for every browser. Extension.js lets you define browser-specific values inline with prefixes, then emits only the fields that match the active target at compile time.

Why this matters

Browsers still differ in key manifest areas, like background configuration and vendor metadata. Prefixed fields let you keep one source manifest.json while producing browser-correct output for Chromium-family and Firefox-family targets.

How it works

Extension.js scans manifest keys and resolves prefixed entries for the selected browser. Resolution is per engine family, not per vendor:
  • Chromium-family targets (chromium, chrome, edge, chromium-based, the forks brave, opera, vivaldi, yandex, and Safari builds) resolve: chromium:, chrome:, edge:
  • Gecko-family targets (firefox, gecko-based, and the forks waterfox, librewolf) resolve: firefox:, gecko:
When a prefixed key matches the active target, Extension.js rewrites it to the unprefixed key in the emitted manifest. Fork targets inherit their engine family’s prefixes, so a manifest that only carries chromium:/firefox: keys still resolves correctly when you target a fork like brave or waterfox. An exact browser-name prefix also matches its own target (for example, brave: when you run --browser=brave).

For Chromium-based browsers (Chrome, Edge, …)

For Firefox

This makes service_worker available only for Chromium-family outputs while keeping background.scripts for Firefox outputs. Supported prefix map: An exact browser-name prefix (for example, brave:, vivaldi:, or waterfox:) additionally resolves only when you target that same browser, and wins over its family prefix (chrome: beats chromium: when targeting chrome). Safari builds inherit the Chromium family (the converter consumes a Chrome-shaped manifest), so chromium:/chrome:/edge: keys apply to Safari too; use safari: (or webkit:) for Safari-only overrides, and they take precedence over the family keys. This works for any manifest field at any level, including permissions, content_scripts, and background.

Family-wide resolution, not per vendor

chromium:, chrome:, and edge: are family prefixes within the Chromium family: each of them applies to every Chromium-family target. If you need a field for one vendor only, use its exact browser-name prefix (for example, brave:), which resolves only for that target.

Precedence: the three-tier lattice

When several keys set the same field, the winner is decided by tier, not by position in the file:
  1. A plain key is the base.
  2. A family prefix (chromium:, chrome:, edge: on Chromium targets, firefox:, gecko: on Gecko targets) overrides the plain key.
  3. A specific prefix overrides both. Specific means the prefix names the exact target, such as chrome: when you build for chrome, or brave: when you build for brave. On Safari and webkit-based targets, safari: and webkit: are both specific.
Source order only breaks ties inside one tier. Consider:
Building for chrome emits "First": chrome: names the exact target, so it sits in the specific tier and beats edge:, which is only a family match there. Building for edge emits "Second" for the mirror-image reason. Building for chromium or brave emits "Second", because both prefixes are family matches and the later one in source order wins the tie. A matching prefixed key always overrides a plain key with the same name, regardless of where each appears in the file.

Prefixes resolve at every depth

The resolver walks the whole manifest tree, including arrays. A prefixed key inside a content_scripts entry, or inside any nested object, resolves by the same three-tier rule as a top-level key.

The same resolver drives entry discovery

Prefix resolution is not only about the emitted JSON. The same resolver runs before script and HTML entry discovery, so a firefox:background script or a prefixed page becomes a compiled entry only on matching targets.

Forks and *-based aliases

Family classification matches by substring, not by a closed list. Any browser name containing chromium classifies as Chromium-family, and any name containing gecko or firefox classifies as Gecko-family. That is why the chromium-based and gecko-based aliases, and arbitrary *-based names built on them, inherit their family’s prefixed keys.

Best practices

  • Keep shared defaults unprefixed: Put common fields in regular manifest keys, then prefix only browser-specific differences.
  • Prefix only when behavior diverges: Use browser prefixes when runtime requirements differ.
  • Build per target in continuous integration (CI): Generate and verify each browser output (dist/<browser>) to catch compatibility regressions early.
  • Validate with MDN: Use MDN Web Docs to confirm support before adding browser-only settings.

Next steps