# How the CRXJS Browser Option Configuration Impacts Firefox vs. Chrome Extension Builds

> Discover how CRXJS browser option configuration impacts Firefox vs Chrome extension builds. Learn to adapt service workers and manifest flags for cross-browser compatibility.

- Repository: [crxjs/chrome-extension-tools](https://github.com/crxjs/chrome-extension-tools)
- Tags: internals
- Published: 2026-02-28

---

**The `browser` option in the CRXJS Vite plugin defaults to `"chrome"` but can be set to `"firefox"` to automatically transform service workers into background scripts and strip Chrome-specific flags like `use_dynamic_url` from the generated manifest.**

The **browser option configuration** is a critical setting in `crxjs/chrome-extension-tools` that determines how your extension manifest and background scripts are generated during the build process. By passing this option to `defineManifest`, you instruct the Vite plugin to emit browser-specific assets that comply with either Chrome's Manifest V3 service worker model or Firefox's background page requirements. This single configuration value triggers a cascade of transformations across multiple internal plugins to ensure cross-browser compatibility from a single source codebase.

## What the Browser Option Controls

Setting the **browser configuration** value to `"firefox"` or `"chrome"` (the default) directly impacts four major areas of the build output:

### Background Script Architecture

- **Chrome (`browser: "chrome"`)**: Generates a **service worker** using `manifest.background.service_worker` with optional ES module support. The plugin emits a loader script that imports the worker file directly.
- **Firefox (`browser: "firefox"`)**: Generates a **background page** using `manifest.background.scripts` because Firefox does not support service workers for extensions. The plugin switches the loader to import a standard background page script instead.

### Web-Accessible Resources Handling

The `web_accessible_resources` array is constructed differently based on the target browser:

- **Chrome**: Adds the `use_dynamic_url` flag to resources, allowing the extension origin to change on each reload for security purposes.
- **Firefox**: **Strips `use_dynamic_url`** from every entry because Firefox forbids this property and treats resources as globally accessible by default.

### Manifest Type Definitions

The plugin uses distinct TypeScript interfaces defined in [`packages/vite-plugin/src/node/manifest.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/manifest.ts) (lines 19‑28):

- `ChromeManifestBackground` for Chrome builds
- `FirefoxManifestBackground` for Firefox builds

### Loader File Generation

During development and production builds, the plugin emits different loader scripts in [`packages/vite-plugin/src/node/plugin-background.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/plugin-background.ts) (lines 62‑88). For Chrome, it imports the service worker environment; for Firefox, it imports the background page environment with HMR support.

## How Browser Detection Works Under the Hood

The **browser option configuration** is processed during three distinct phases of the Vite lifecycle:

### 1. Configuration Resolution (`config` Hook)

Both the background and web-accessible resources plugins read the option during the `config` hook using `opts.browser || 'chrome'` as seen in [`packages/vite-plugin/src/node/plugin-webAccessibleResources.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/plugin-webAccessibleResources.ts) (lines 36‑39) and [`packages/vite-plugin/src/node/plugin-background.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/plugin-background.ts) (lines 48‑52). If you omit the option, it safely defaults to Chrome.

### 2. Development Mode (`apply: 'serve'`)

In serve mode, the plugins emit temporary loader scripts that inject environment variables and HMR clients. The background plugin checks the browser value to determine whether to import a service worker or a background page script ([`packages/vite-plugin/src/node/plugin-background.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/plugin-background.ts), lines 62‑88). Meanwhile, the web-accessible resources plugin adds a generic `<all_urls>` entry but conditionally deletes `use_dynamic_url` for Firefox targets.

### 3. Production Build (`apply: 'build'`)

During the build phase, the web-accessible resources plugin analyzes the Vite file manifest, deduplicates assets, and iterates over the resource list to strip `use_dynamic_url` when targeting Firefox ([`packages/vite-plugin/src/node/plugin-webAccessibleResources.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/plugin-webAccessibleResources.ts), lines 25‑30 and 63‑66). The final manifest is returned from `renderCrxManifest` with all Firefox-specific normalizations applied.

## Code Examples

### Configuring the Browser Option

Define your target browser in the manifest configuration file:

```typescript
// manifest.config.ts
import { defineManifest } from '@crxjs/vite-plugin'

export default defineManifest({
  manifest_version: 3,
  name: 'My Extension',
  version: '1.0.0',
  // Target Firefox instead of Chrome
  browser: 'firefox',
  background: {
    scripts: ['src/background.ts'],
  },
})

```

When `browser: 'firefox'` is present, the generated [`manifest.json`](https://github.com/crxjs/chrome-extension-tools/blob/main/manifest.json) will contain `background.scripts` instead of `background.service_worker`, and `web_accessible_resources` entries will omit the `use_dynamic_url` property.

### Generated Output for Chrome

```json
{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "web_accessible_resources": [
    {
      "matches": ["<all_urls>"],
      "resources": ["**/*", "*"],
      "use_dynamic_url": false
    }
  ]
}

```

### Generated Output for Firefox

```json
{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "background": {
    "scripts": ["background.js"]
  },
  "web_accessible_resources": [
    {
      "matches": ["<all_urls>"],
      "resources": ["**/*", "*"]
    }
  ],
  "browser_specific_settings": {
    "gecko": {
      "id": "my-extension@example.com"
    }
  }
}

```

Note the absence of `use_dynamic_url` and the presence of `background.scripts` in the Firefox output.

## Key Source Files and Implementation Details

The **browser option configuration** logic is distributed across these critical files in the `crxjs/chrome-extension-tools` repository:

- **[`packages/vite-plugin/src/node/types.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/types.ts)** (lines 90‑96): Declares the `Browser` union type (`'chrome' | 'firefox'`) and the optional `browser?` field within `CrxOptions`.
- **[`packages/vite-plugin/src/node/plugin-background.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/plugin-background.ts)** (lines 48‑61): Reads the browser option and switches between generating `service_worker` entries for Chrome and `scripts` arrays for Firefox.
- **[`packages/vite-plugin/src/node/plugin-webAccessibleResources.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/plugin-webAccessibleResources.ts)** (lines 25‑30, 36‑39, 63‑66): Handles the conditional removal of `use_dynamic_url` during both serve and build phases.
- **[`packages/vite-plugin/src/node/manifest.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/manifest.ts)** (lines 19‑28): Defines the `ChromeManifestBackground` and `FirefoxManifestBackground` interfaces that enforce type safety across the build pipeline.

## Summary

- The **browser option configuration** defaults to `"chrome"` but accepts `"firefox"` to trigger cross-browser compatibility transformations.
- Firefox builds use `background.scripts` instead of service workers and strip `use_dynamic_url` from web-accessible resources.
- The option is read during the Vite `config` hook in [`plugin-background.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/plugin-background.ts) and [`plugin-webAccessibleResources.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/plugin-webAccessibleResources.ts), with logic applied during both serve and build phases.
- Type definitions in [`manifest.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/manifest.ts) enforce distinct manifest schemas for each browser target.
- A single source configuration can output valid Manifest V3 extensions for both Chrome and Firefox by changing one configuration value.

## Frequently Asked Questions

### How do I switch from Chrome to Firefox in my CRXJS project?

Set `browser: 'firefox'` in your `defineManifest` configuration. According to the source code in [`packages/vite-plugin/src/node/types.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/types.ts), this optional field accepts either `"chrome"` (default) or `"firefox"`. The plugin automatically transforms service workers into background scripts and sanitizes the manifest for Firefox compatibility.

### Why does my Firefox build fail with "use_dynamic_url is not allowed"?

Firefox forbids the `use_dynamic_url` property in `web_accessible_resources`. When you set `browser: 'firefox'`, the plugin in [`packages/vite-plugin/src/node/plugin-webAccessibleResources.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/plugin-webAccessibleResources.ts) (lines 63‑66) automatically strips this flag during the build. If you see this error, verify that you have explicitly set the browser option to `"firefox"` in your manifest config.

### Can I build for both browsers simultaneously from the same source?

Not in a single build command. The **browser option configuration** is a build-time directive that generates a single target-specific manifest. To support both browsers, maintain separate Vite config files or use environment variables to switch the browser value between build runs, allowing you to output distinct Chrome and Firefox packages from the same codebase.

### Does the browser option affect the HMR (Hot Module Replacement) behavior?

Yes. During development mode (`apply: 'serve'`), the background plugin in [`packages/vite-plugin/src/node/plugin-background.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/plugin-background.ts) (lines 62‑88) emits different loader scripts based on the browser value. Chrome receives a service worker loader with HMR support, while Firefox receives a background page loader that handles HMR for standard scripts rather than service workers.