Why this matters
Browsers still differ in key manifest areas, like background configuration and vendor metadata. Prefixed fields let you keep one sourcemanifest.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. A prefix names an engine family or one browser:chromium:reaches every Chromium-family target (chromium,chrome,edge,chromium-based, the forksbrave,opera,vivaldi,yandex, and Safari builds)chrome:reaches onlychrome, andedge:reaches onlyedgefirefox:andgecko:reach every Gecko-family target (firefox,gecko-based, and the forkswaterfox,librewolf)
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
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,
chrome:, edge:, brave:, or waterfox:) resolves only when you target that same browser. It wins over its family prefix, so chrome: beats chromium: when you build for chrome.
Safari builds inherit the Chromium family, because the converter consumes a Chrome-shaped manifest. chromium: keys apply to Safari, but chrome: and edge: keys do not. Use safari: (or webkit:) for Safari-only overrides. They take precedence over chromium: keys.
Keys and permissions that Safari does not implement are dropped from the Safari build automatically. From 4.1.20, the build prints one line per dropped key, naming the key and why it went. content_scripts[].world is kept, because Safari has supported it since Safari 18. See Building Safari extensions.
This works for any manifest field at any level, including permissions, content_scripts, and background.
Target one Chromium vendor
chromium: is the family prefix. chrome: and edge: each name one browser, so a field can ship to one store and stay out of the other. For example, a Chrome Web Store key must not reach the Edge Add-ons package:
extension build --browser=chrome emits key. extension build --browser=edge leaves it out.
Do not write edge:key. Edge Add-ons rejects any package whose manifest contains key at all, so the field has no use in an Edge build. Partner Center assigns the extension id instead. From 4.1.20, a production Edge build drops key and prints one line saying why. A development build keeps it, where a stable id is useful and no store is involved.
The prefix matches the browser that you request with --browser, not the binary that launches. When Extension.js falls back to another browser binary, prefixes still resolve for the requested target.
In Extension.js 4.1.19,
chrome: and edge: became exact browser prefixes. Up to 4.1.18, they reached every Chromium-family target. When a build drops a chrome: or edge: key that 4.1.18 applied, the build prints a warning that names the key. Rename the key to chromium: to keep the old reach.Precedence: the three-tier lattice
When several keys set the same field, the winner is decided by tier, not by position in the file:- A plain key is the base.
- A family prefix (
chromium:on Chromium targets,firefox:andgecko:on Gecko targets) overrides the plain key. - A specific prefix overrides both. Specific means that the prefix names the exact target, such as
chrome:for achromebuild orbrave:for abravebuild. On Safari and webkit-based targets,safari:andwebkit:are both specific.
chrome emits "Chrome". chrome: names the exact target, so it sits in the specific tier and beats chromium:. Building for edge, chromium, or brave emits "Family", because chrome: does not apply to those targets.
A tie needs two prefixes in the same tier, such as firefox: and gecko: on a Gecko fork like waterfox, or safari: and webkit: on a Safari target. The later key in source order wins.
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 acontent_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 afirefox:background script or a prefixed page becomes a compiled entry only on matching targets.
Forks and *-based aliases
Family classification matches a known-fork list first, then falls back to a substring check. chrome, edge, brave, opera, vivaldi and yandex classify as Chromium-family by name, as does any other name containing chromium. firefox, waterfox and librewolf classify as Gecko-family by name, as does any other name containing gecko or firefox. That is why the chromium-based and gecko-based aliases, and arbitrary *-based names built on them, inherit their family’s prefixed keys.
A prefixed manifest_version needs a plain fallback
chrome: and edge: are exact prefixes, so a manifest_version scoped to one vendor leaves every other build without one. This manifest gives Firefox and Chrome a version and gives Edge none:
manifest_version. From 4.1.21 the build refuses that case with an error that names the dropped key, for example chrome:manifest_version applies only to Chrome builds, so the edge build has no manifest_version. Up to 4.1.20 the build wrote the manifest and the browser rejected it later.
Write a plain manifest_version as the base, and prefix only the exception:
chromium:manifest_version when the whole Chromium family should share one value that differs from the plain key.
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
- Learn more about the Browsers available.
- Learn more about Cross-browser compatibility.

