Extension config (extension.config.js)

Use one config file to set browser defaults, command behavior, and bundler customization.

Share browser defaults, command options, and build settings across your team without repeating CLI flags. Extension.js reads extension.config.js (or .mjs / .cjs) from your project root and applies it to dev, start, preview, build, and bundler config.

How it works

Add extension.config.js at your project root (same level as package.json in typical setups).

Supported file names:

  • extension.config.js
  • extension.config.mjs
  • extension.config.cjs

Top-level keys:

KeyDescription
browserBrowser-specific defaults keyed by browser target.
commandsPer-command defaults (dev, start, preview, build).
extensionsCompanion extensions applied across commands (load-only).
transpilePackagesDefault package transpile allowlist (monorepo/workspace support).
configHook to extend/override generated webpack/rspack config.

Environment loading for config files

extension.config.* runs in Node and should read values from process.env.*.

  • Extension.js preloads env files before evaluating extension.config.*.
  • It first checks the project directory.
  • In monorepos, if no project-local .env* file is found, it falls back to the nearest workspace root (directory containing pnpm-workspace.yaml) and applies the same order there.
  • Prefer built-in env preload over importing dotenv in your config file.

Browser configuration

Need different browser defaults per target? Use browser:

export default {
  browser: {
    chrome: {
      profile: 'path/to/profile',
      preferences: {darkMode: true},
      browserFlags: ['--disable-web-security'],
      excludeBrowserFlags: ['--mute-audio']
    },
    'chromium-based': {
      chromiumBinary: '/path/to/brave-or-other-chromium-binary'
    },
    firefox: {
      geckoBinary: '/path/to/firefox'
    }
  }
}

Supported browser keys include: chrome, edge, firefox, chromium, chromium-based, gecko-based.

Common browser fields:

  • profile, persistProfile
  • preferences
  • browserFlags, excludeBrowserFlags
  • chromiumBinary, geckoBinary
  • extensions (companion load-only extensions)

Browser target capabilities

KeyWhat it does
profileSets the browser profile path (or profile behavior) used when launching.
persistProfileReuses profile data between runs.
preferencesApplies browser preference overrides.
browserFlagsAdds launch flags for the browser process.
excludeBrowserFlagsRemoves default launch flags you do not want.
chromiumBinaryUses a custom Chromium-family binary path.
geckoBinaryUses a custom Gecko/Firefox binary path.
extensionsLoads companion extensions together with the main extension.

Commands configuration

Use commands to define defaults per command:

export default {
  transpilePackages: ['@workspace/ui'],
  commands: {
    dev: {
      browser: 'chrome',
      polyfill: true,
      logLevel: 'info',
      persistProfile: true,
      browserFlags: ['--headless=new'],
      extensions: {dir: './extensions'}
    },
    start: {
      browser: 'firefox',
      source: 'https://example.com'
    },
    preview: {
      browser: 'edge',
      noBrowser: false
    },
    build: {
      browser: 'chrome',
      zip: true,
      zipSource: true,
      zipFilename: 'my-extension.zip',
      transpilePackages: ['@workspace/ui', '@workspace/icons']
    }
  }
}

Notes:

  • Top-level extensions and transpilePackages are merged into command defaults.
  • Per-command values override top-level values.
  • start is supported the same way as dev/preview/build.

Command capabilities (shared)

KeyApplies toWhat it does
browserdev, start, preview, buildSets browser or browser-family target.
profiledev, start, previewSets browser profile path/behavior.
persistProfiledev, start, previewReuses browser profile data between runs.
chromiumBinarydev, start, previewUses a custom Chromium-family binary.
geckoBinarydev, start, previewUses a custom Gecko-family binary.
polyfilldev, start, buildEnables compatibility polyfill behavior where relevant.
startingUrldev, start, previewOpens a specific URL on browser launch.
portdev, start, previewSets runner/devtools port.
noBrowserdev, start, previewDisables browser launch.
extensionsdev, start, preview, buildLoads companion extensions (or extension list).
installdev, start, buildInstalls missing dependencies before running.
transpilePackagesdev, start, preview, buildTranspiles listed workspace/external packages.

build command capabilities

KeyWhat it does
zipGenerates a packaged zip artifact.
zipSourceAdds source bundle/archive output for review/compliance workflows.
zipFilenameSets a custom zip output filename.
silentReduces build log output.

dev command capabilities

KeyWhat it does
noOpenRuns dev server without auto-opening browser.
sourceEnables source-inspection flow from a remote source URL.
watchSourceRe-runs source inspection when watched updates occur.
sourceFormatSets source output format (pretty, json, ndjson).
sourceSummaryEmits compact source summary output.
sourceMetaIncludes page metadata in source output.
sourceProbeProbes source output with specific CSS selectors.
sourceTreeControls compact extension tree output.
sourceConsoleIncludes console summary in source output.
sourceDomIncludes DOM snapshots/diffs in source output.
sourceMaxBytesCaps source output payload size.
sourceRedactControls source redaction strategy.
sourceIncludeShadowControls Shadow DOM inclusion strategy.
sourceDiffIncludes diff metadata on source watch updates.

Logging capabilities

KeyApplies toWhat it does
logsdev, start, previewSets minimum log level (off to all).
logContextdev, start, previewFilters logs by context (background, content, etc.).
logFormatdev, start, previewSets output format (pretty, json, ndjson).
logUrldev, start, previewFilters logs by URL pattern.
logTabdev, start, previewFilters logs by tab ID.

Webpack configuration

Need advanced bundler customization? Use config to patch the generated config:

export default {
  config: (config) => {
    config.module.rules.push({
      test: /\.mdx$/,
      use: ['babel-loader', '@mdx-js/loader']
    })
    return config
  }
}

config may also be an object, which is merged on top of the generated config.

Full sample

export default {
  browser: {
    chrome: {browser: 'chrome', profile: 'default'},
    firefox: {browser: 'firefox', persistProfile: true},
    'chromium-based': {
      browser: 'chromium-based',
      chromiumBinary:
        '/Applications/Brave Browser.app/Contents/MacOS/Brave Browser'
    }
  },
  commands: {
    dev: {
      browser: 'chrome',
      polyfill: true,
      extensions: {dir: './extensions'}
    },
    start: {browser: 'edge'},
    preview: {browser: 'firefox'},
    build: {
      zipFilename: 'extension.zip',
      zip: true,
      zipSource: true
    }
  },
  extensions: {dir: './extensions'},
  transpilePackages: ['@workspace/ui'],
  config: (config) => config
}

Best practices

  • Keep browser-specific values in browser: Keep command definitions focused on workflow, not browser internals.
  • Use top-level defaults intentionally: Put shared extensions / transpilePackages at root; override only where needed.
  • Prefer chromiumBinary/geckoBinary names: They align with current command and type surface.
  • Keep config hook minimal: Add only what cannot be expressed through first-class Extension.js options.

Next steps