> ## Documentation Index
> Fetch the complete documentation index at: https://extension.js.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Browser-specific manifest fields

> Define Chrome, Firefox, and Edge manifest values inline with browser prefixes. Extension.js emits only the fields matching each target at build time.

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. A prefix names an engine family or one browser:

* `chromium:` reaches every Chromium-family target (`chromium`, `chrome`, `edge`, `chromium-based`, the forks `brave`, `opera`, `vivaldi`, `yandex`, and Safari builds)
* `chrome:` reaches only `chrome`, and `edge:` reaches only `edge`
* `firefox:` and `gecko:` reach every Gecko-family target (`firefox`, `gecko-based`, and the forks `waterfox`, `librewolf`)

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, ...)

```json theme={null}
{
  "chromium:background": {
    "service_worker": "sw.js"
  }
}
```

### For Firefox

```json theme={null}
{
  "firefox:background": {
    "scripts": ["sw.js"]
  }
}
```

This makes `service_worker` available only for Chromium-family outputs while keeping `background.scripts` for Firefox outputs.

Supported prefix map:

| Prefix | Included for target browser |
| - | - |
| `chromium:` | Every Chromium-family target: `chromium`, `chrome`, `edge`, `chromium-based`, forks, Safari builds |
| `chrome:` | `chrome` only |
| `edge:` | `edge` only |
| `firefox:` | `firefox`, `gecko-based`, `firefox-based`, `waterfox`, `librewolf` |
| `gecko:` | `firefox`, `gecko-based`, `firefox-based`, `waterfox`, `librewolf` |
| `safari:` | `safari` and `webkit-based`, wins over `chromium:` keys on those targets |
| `webkit:` | `safari` and `webkit-based` (same behavior as `safari:`) |

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](/docs/browsers/safari).

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:

```json theme={null}
{
  "chrome:key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
}
```

`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.

<Note>
  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.
</Note>

### 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:` on Chromium targets, `firefox:` and `gecko:` on Gecko targets) overrides the plain key.
3. A specific prefix overrides both. Specific means that the prefix names the exact target, such as `chrome:` for a `chrome` build or `brave:` for a `brave` build. On Safari and webkit-based targets, `safari:` and `webkit:` are both specific.

Source order only breaks ties inside one tier. Consider:

```json theme={null}
{
  "chromium:action": {
    "default_title": "Family"
  },
  "chrome:action": {
    "default_title": "Chrome"
  }
}
```

Building for `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 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 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:

```json theme={null}
{
  "firefox:manifest_version": 2,
  "chrome:manifest_version": 3
}
```

No browser loads a manifest without `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:

```json theme={null}
{
  "manifest_version": 3,
  "firefox:manifest_version": 2
}
```

Use `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](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions) to confirm support before adding browser-only settings.

## Next steps

* Learn more about the [Browsers available](/docs/browsers/browsers-available).
* Learn more about [Cross-browser compatibility](/docs/features/cross-browser-compatibility).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.