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

# Manifest.json compilation and output

> Use manifest.json as the source of truth for entrypoints and assets. Extension.js compiles, rewrites paths, and emits a browser-ready manifest.

Keep extension builds predictable by treating `manifest.json` as the source of truth for entrypoints, assets, and browser-specific behavior.

Extension.js compiles your manifest and filters browser-prefixed fields. It rewrites runtime paths, validates referenced files, and produces a ready-to-load manifest for each target browser.

## Manifest capabilities

| Capability | What it gives you |
| - | - |
| Browser-specific field filtering | Keep one manifest file while emitting target-specific output |
| Path normalization | Resolve runtime-safe output paths automatically |
| Reference validation | Fail early when HTML/script/CSS/JSON/icon files are missing |
| Targeted outputs | Generate manifest artifacts per browser target in `dist/<browser>` |

## Where Extension.js reads the manifest

* `src/manifest.json` (preferred when present)
* `manifest.json` at project root

Extension.js does not use `public/manifest.json` as the source manifest.

A `manifest.json` under `public/` fails the build with `manifest.json must not be placed under public/`. Move it to `src/manifest.json` or the project root, so a copied static asset never overwrites the manifest that Extension.js generates.

## What Extension.js does with it

During dev/build, the manifest pipeline:

1. Emits the manifest asset from your source file.
2. Filters browser-prefixed keys for the active browser target.
3. Applies manifest overrides/path normalization for extension outputs.
4. Validates referenced files (HTML/scripts/CSS/icons/JSON) and fails early when missing.

## One manifest, multiple browsers

Browser-prefixed keys let you keep one manifest file while still targeting browser-specific behavior:

* `chromium:*` for every Chromium-family browser, `chrome:*` and `edge:*` for one browser each
* `firefox:*`, `gecko:*`

These prefixes can apply to top-level keys and nested manifest fields.

Examples:

* `chromium:key`
* `background.firefox:scripts`
* `background.chromium:service_worker`

### Supported manifest fields

Common entrypoint-related fields include:

| Manifest field | File type expected |
| - | - |
| `action.default_popup` | .html |
| `background.page` | .html |
| `background.service_worker` | .js, .jsx, .ts, .tsx, .mjs |
| `browser_action.default_popup` | .html |
| `chrome_url_overrides.bookmarks` | .html |
| `chrome_url_overrides.history` | .html |
| `chrome_url_overrides.newtab` | .html |
| `content_scripts.js` | .js, .jsx, .ts, .tsx, .mjs |
| `content_scripts.css` | .css, .scss, .sass, .less |
| `declarative_net_request.rule_resources` | .json |
| `devtools_page` | .html |
| `icons` | .png, .jpg, ...Other image formats |
| `options_ui.page` | .html |
| `options_page` | .html |
| `page_action.default_popup` | .html |
| `sandbox.pages` | .html |
| `side_panel.default_path` | .html |
| `sidebar_action.default_panel` | .html |
| `storage.managed_schema` | .json |
| `theme_icons` | .png, .jpg, ...Other image formats |
| `user_scripts.api_script` | .js, .jsx, .ts, .tsx, .mjs |
| `web_accessible_resources` | .png, .jpg, .css, .js |

## Permissions design

The manifest is also where your extension declares what it can do. Extension.js compiles the manifest, but you still need good permission design.

* Keep `permissions` small and intentional.
* Keep `host_permissions` as narrow as the feature allows.
* Move non-core capabilities into `optional_permissions` or `optional_host_permissions` where possible.
* Review permission scope whenever content-script matches or background capabilities change.

For permission strategy, see [Permissions and host permissions](/docs/implementation-guide/permissions-and-host-permissions).

## Output behavior

Extension.js rewrites manifest paths to predictable output locations when needed. Two important examples:

* `background.service_worker` becomes `background/service_worker.js`
* `side_panel.default_path` becomes `sidebar/index.html`
* `page_action.default_popup` becomes `page_action/index.html`, its own page beside the toolbar popup, on Firefox (any manifest version) and on Chromium MV2. When `page_action` and `action` (or `browser_action`) name the same file, both keys share `action/index.html`. Chromium MV3 has no page action surface, so the build drops `page_action` from that manifest with a warning and emits no page for it.

Extension.js also normalizes content scripts by manifest entry index:

* `content_scripts/content-0.js`
* `content_scripts/content-0.css`

Those emitted paths are what the browser actually loads. Use source paths in authoring, then let Extension.js rewrite them for output.

### Manifest V2 builds fold host permissions and flatten the CSP

From 4.1.18, every build with `manifest_version: 2` reshapes two Manifest V3 keys, on every browser target. Your source manifest stays as you wrote it.

`host_permissions` folds into `permissions`, and `optional_host_permissions` folds into `optional_permissions`. Each list is deduplicated, and the two MV3 keys are dropped from the emitted manifest. Manifest V2 reads match patterns from `permissions`, so this is where Firefox expects them.

`content_security_policy` is emitted as one string, taken from the `extension_pages` slot of the object form. Manifest V2 has no place for the `sandbox` slot, so that policy is dropped with a warning:

```plaintext theme={null}
firefox reads a Manifest V2 content_security_policy as one string, so the sandbox slot has nowhere to go.
The build wrote the extension_pages policy as that string and dropped the sandbox policy from the built manifest. Scope the object form with the chromium: prefix, or declare a Manifest V3 build for this browser.
```

For example, this source:

```json theme={null}
{
  "manifest_version": 2,
  "permissions": ["storage"],
  "host_permissions": ["https://example.com/*"],
  "content_security_policy": {
    "extension_pages": "script-src 'self'"
  }
}
```

emits `"permissions": ["storage", "https://example.com/*"]` and `"content_security_policy": "script-src 'self'"`, with no `host_permissions` key.

## Development behavior

* When `manifest.json` changes, Extension.js recompiles and triggers extension hard reload flow.
* If manifest entrypoint structure changes (for example, script list changes), Extension.js may require a dev server restart.
* Missing files referenced by manifest fields fail compilation with manifest-focused errors.

### Change outcome matrix

| Manifest change type | Typical outcome |
| - | - |
| Update non-structural values (for example, descriptions/permissions metadata) | Hard reload flow |
| Update asset path values that still resolve cleanly | Recompile + hard reload flow |
| Add/remove script or page entrypoints in manifest | Restart required |
| Introduce invalid/missing referenced files | Build error (fix first, then rerun) |

## What Extension.js repairs for you

Some manifest shapes make Chromium refuse to load the extension outright, with little explanation. Extension.js diagnoses these before the browser launches and auto-repairs the fatal ones. Each repair prints a warning that names the field and the reason. See [Manifest refusals](/docs/debugging/manifest-refusals) for the catalogue of refusal causes and repairs.

### Legacy path warnings

Development and production builds both warn when the emitted manifest still contains one of these deprecated generated paths:

* `devtools_page/devtools_page.html`
* `options_ui/page.html`
* `background/page.html`
* `browser_action/default_popup.html`
* `page_action/default_popup.html`
* `side_panel/default_path.html`
* `sidebar_action/default_panel.html`

Each match produces a `ManifestLegacyWarning`. Extension.js rewrites these paths to the standardized folders in the next major version.

## Best practices

* Keep manifest paths relative to the extension source/output model and use leading `/` only when you mean extension output root.
* Use browser-prefixed keys instead of maintaining separate manifest files per browser.
* Keep entrypoint changes deliberate; adding/removing manifest scripts often changes reload semantics in dev.
* Validate icons, JSON resources, and content-script assets as part of continuous integration (CI) to catch path regressions early.
* Do not place `manifest.json` under `public/`.

## Next steps

* Understand update outcomes in [dev update behavior](/docs/workflows/dev-update-behavior).
* Design least-privilege access in [Permissions and host permissions](/docs/implementation-guide/permissions-and-host-permissions).
* Learn how Extension.js handles [Browser-specific manifest fields](/docs/features/browser-specific-fields).
* Understand dev update flow in [Page reload and hot module replacement (HMR)](/docs/features/reload-and-hmr).

## Video walkthrough

<Frame>
  <iframe className="w-full aspect-video rounded-xl" src="https://www.youtube-nocookie.com/embed/AWMlWK934qE?rel=0" title="Extension.js: Action (Popup) template" loading="lazy" allow="encrypted-media; picture-in-picture; fullscreen" allowFullScreen />
</Frame>


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