Multiple environments

Set up development, staging, and production environment variables for the browser extension.

For the complete documentation index, see llms.txt. Prefer markdown by appending .md to documentation URLs or sending Accept: text/markdown.

Browser extensions are shipped as static bundles, so any value exposed to the extension can be inspected by users. Treat extension environment variables as public configuration and keep secrets behind your web/API server.

The safe pattern is:

  • use modes for environments, such as development, staging, and production
  • use browser-specific files only when Chrome, Firefox, Edge, or Safari need different values
  • expose runtime values with VITE_ or WXT_
  • keep signing keys and store credentials in CI secrets, not extension env files

No extension secrets

Do not put private API keys, database URLs, webhook secrets, or service-role tokens in the extension. If the extension needs a privileged action, call your authenticated web/API endpoint.

Choose your environment values

Start with the values the extension needs to know:

apps/extension/.env.example
VITE_APP_ENV="development"
VITE_SITE_URL="http://localhost:3000"
VITE_DEFAULT_LOCALE="en"
VITE_THEME_MODE="system"
VITE_THEME_COLOR="orange"

Use the same names in every environment so your code does not need environment-specific branches.

Create mode-specific files

WXT follows Vite-style env loading and supports mode-specific files:

apps/extension/.env.development.local
VITE_APP_ENV="development"
VITE_SITE_URL="http://localhost:3000"
VITE_DEFAULT_LOCALE="en"
VITE_THEME_MODE="system"
VITE_THEME_COLOR="orange"
apps/extension/.env.staging.local
VITE_APP_ENV="staging"
VITE_SITE_URL="https://staging.example.com"
VITE_DEFAULT_LOCALE="en"
VITE_THEME_MODE="system"
VITE_THEME_COLOR="orange"
apps/extension/.env.production.local
VITE_APP_ENV="production"
VITE_SITE_URL="https://example.com"
VITE_DEFAULT_LOCALE="en"
VITE_THEME_MODE="system"
VITE_THEME_COLOR="orange"

Use .local files for machine-specific or sensitive values and keep them ignored.

Add browser-specific overrides only when needed

If a browser needs different values, add the browser to the filename:

apps/extension/.env.production.firefox.local
VITE_SITE_URL="https://example.com"
VITE_FIREFOX_EXTENSION_ID="extension@example.com"

WXT can load files by mode and browser, for example:

  • .env.production
  • .env.production.local
  • .env.firefox
  • .env.production.firefox
  • .env.production.firefox.local

Keep the shared values in mode files and use browser files only for browser-specific IDs, permissions, or store behavior.

Use env values in extension code

Read public configuration through import.meta.env:

apps/extension/utils/config.ts
export const config = {
  appEnv: import.meta.env.VITE_APP_ENV,
  siteUrl: import.meta.env.VITE_SITE_URL,
  defaultLocale: import.meta.env.VITE_DEFAULT_LOCALE,
};

When using env values inside the manifest, use the function form so WXT can load env files first:

apps/extension/wxt.config.ts
import { defineConfig } from "wxt";

export default defineConfig({
  manifest: ({ mode }) => ({
    name: mode === "production" ? "Acme" : `Acme (${mode})`,
    host_permissions: [`${import.meta.env.VITE_SITE_URL}/*`],
  }),
});

Add scripts for each target

Use --mode for the environment and -b for the browser target:

apps/extension/package.json
{
  "scripts": {
    "dev:chrome": "wxt -b chrome --mode development",
    "dev:firefox": "wxt -b firefox --mode development",
    "build:chrome:staging": "wxt build -b chrome --mode staging",
    "build:firefox:staging": "wxt build -b firefox --mode staging",
    "build:chrome:production": "wxt build -b chrome --mode production",
    "build:firefox:production": "wxt build -b firefox --mode production"
  }
}

Then run:

pnpm --filter extension build:chrome:staging
pnpm --filter extension build:firefox:production

Configure CI secrets separately

Environment variables that build the bundle are different from secrets used to publish it.

Keep bundle-safe values as normal CI environment variables:

.github/workflows/publish-extension.yml
env:
  VITE_APP_ENV: production
  VITE_SITE_URL: https://example.com

Keep store credentials and signing keys as CI secrets:

.github/workflows/publish-extension.yml
env:
  CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
  CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
  CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
  CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}

Before release, verify:

  • VITE_SITE_URL points to the matching web/API environment
  • extension origins are allowed by auth and CORS settings
  • browser-specific IDs match the store listing
  • staging builds use staging API, analytics, and auth settings
  • no private values are prefixed with VITE_ or WXT_

Useful references

How is this guide?

Last updated on

On this page

Ship your startup everywhere. In minutes.Try TurboStarter