Skip to content

CLI commands

ExtForge’s CLI is invoked as extforge <command>. All commands read extforge.config.ts from the current working directory unless otherwise noted.


Create a new browser extension project from an interactive scaffold.

Terminal window
extforge init [name] [--defaults] [--dir <path>]
Argument / FlagTypeDescription
namepositional (optional)Project name. Prompted if not supplied.
--defaultsbooleanSkip all prompts and use defaults (react, tailwind, chrome firefox, popup background).
--dirstringTarget directory. Defaults to a subdirectory named after the project.

Runs the interactive scaffold:

  1. Prompts for project name, framework (react, vue, svelte, solid, vanilla), CSS (tailwind, vanilla, none), browsers, and features (popup, background, content, options, sidepanel).
  2. Creates the directory structure, extforge.config.ts, package.json, tsconfig.json, and source templates.
  3. Writes .gitignore, vitest.config.ts, and E2E fixtures.
✔ Project name: my-extension
✔ Framework: react
✔ CSS: tailwind
✔ Browsers: chrome, firefox
✔ Features: popup, background
Scaffolding my-extension...
✔ Created my-extension/extforge.config.ts
✔ Created my-extension/src/ui/popup/popup.tsx
✔ Created my-extension/src/background/index.ts
✔ Done. cd my-extension && pnpm install && extforge dev

Start the development server with file watching and HMR.

Terminal window
extforge dev [--browser <name>] [--port <n>] [--host <host>]
[--quiet] [--verbose] [--json] [--once]
FlagDefaultDescription
--browserchromeBrowser target for the dev build.
--port35729HMR WebSocket port.
--hostlocalhostHMR host.
--quietfalseSuppress info-level output.
--verbosefalseLog every file change and reload decision.
--jsonfalseEmit newline-delimited JSON log lines.
--oncefalseRun a single dev build then exit (useful in CI).
  1. Validates the project structure.
  2. Builds the extension in dev mode (source maps on, HMR client injected).
  3. Starts a WebSocket server on --port.
  4. Watches the src/ directory for changes and applies the appropriate reload strategy per change type.

Exits 0 on clean shutdown (SIGINT / SIGTERM). Exits 1 if --once is passed and the build has errors.

See the HMR guide for reload strategies and troubleshooting.


Build the extension for production.

Terminal window
extforge build [--browser <name>] [--dev] [--sourcemap] [--strict] [--quiet] [--json]
FlagDefaultDescription
--browser(all from config)Build a single browser instead of all.
--devfalseDevelopment build (source maps on, HMR client injected).
--sourcemapfalseInclude source maps (implied by --dev).
--strictfalseTreat cross-browser compat warnings as errors.
--quietfalseSuppress info-level output.
--jsonfalseEmit newline-delimited JSON log lines.

Iterates config.browsers (or the single --browser target), runs esbuild on each entry point, generates manifest.json for each browser, and writes output to dist/<browser>/.

Exits 0 on success, 1 if any browser’s build has errors.

✔ chrome: 3 entries bundled in 412ms → dist/chrome/
✔ firefox: 3 entries bundled in 398ms → dist/firefox/

Validate project structure and config without building.

Terminal window
extforge validate [--quiet] [--json]
FlagDefaultDescription
--quietfalseSuppress info-level output.
--jsonfalseEmit newline-delimited JSON log lines.
  1. Checks that extforge.config.ts exists and passes the Zod schema.
  2. Validates the manifest config for required fields and known permission names.
  3. Checks that declared entry-point files exist.

Exits 0 if all checks pass, 1 if any error is found. Warnings print but do not affect the exit code.

✔ Config valid
✔ Manifest: name, version, permissions OK
⚠ Manifest: sidePanel declared but sidePanel permission not in required[]
✔ All checks passed

Diagnose the project and environment, checking for common problems.

Terminal window
extforge doctor [--json] [--quiet]
FlagDefaultDescription
--jsonfalseEmit the full report as a JSON object to stdout.
--quietfalseSuppress info-level output.

Runs a fixed set of checks:

CheckWhat it tests
node-versionNode.js meets the minimum version requirement
config-validConfig file loads and validates without error
icons-presentPNG icons exist at expected sizes (16, 32, 48, 128)
port-freeHMR port (dev.port) is not already bound
dist-gitignoreddist/ is listed in .gitignore
permissions-knownAll declared permissions are in the known MV3 permission list
browser-overridesBrowser override keys are valid browser names
scripts-presentDeclared entry-point files exist on disk
compatNo cross-browser compat issues (warn-only by default)

Each check has a status: pass, warn, fail, or info. doctor exits 0 if no check fails, 1 if any check has fail status.

✔ node-version: Node 22.4.0 meets requirement
✔ config-valid: Config loaded successfully
✖ icons-present: icons/icon-48.png missing
hint: Run `extforge icons` to generate from icons/icon.svg
⚠ compat: chrome.tabGroups.query not supported on safari (src/background/index.ts:12)
Summary pass: 7 warn: 1 fail: 1
{
"v": 1,
"summary": { "pass": 7, "warn": 1, "fail": 1 },
"exitCode": 1,
"results": [
{ "name": "icons-present", "status": "fail", "message": "icons/icon-48.png missing", "hint": "Run `extforge icons`" }
]
}

Create .zip archives of built extensions for store submission.

Terminal window
extforge package [--browser <name>]
FlagDescription
--browserPackage a single browser only.

For each browser in config.browsers (or the single --browser target):

  1. Checks that dist/<browser>/ exists. Skips with a warning if not.
  2. Creates packages/<name>-<browser>-v<version>.zip from all files in the dist directory.

The filename is sanitised: anything outside [a-zA-Z0-9._-] in manifest.name/manifest.version is replaced with _ so no path component can be misinterpreted as a shell argument.

extforge package prefers the system zip binary (faster, more battle-tested) and falls back to a pure-Node ZIP writer when zip isn’t installed — typical on Windows. The fallback produces deterministic, byte-for-byte reproducible archives (fixed DOS timestamp, sorted entries, DEFLATE via node:zlib) and skips .DS_Store / .git automatically. No additional dependencies.

✔ Packaged chrome → packages/My_Extension-chrome-v1.0.0.zip
✔ Packaged firefox → packages/My Extension-firefox-v1.0.0.zip
⚠ No build for safari — run `extforge build` first

Generate PNG icons from an SVG source.

Terminal window
extforge icons

Reads icons/icon.svg and generates icons/icon-16.png, icon-32.png, icon-48.png, and icon-128.png.

Tries sharp-cli first (npx sharp-cli). Falls back to cairosvg (Python). If neither is available, prints an install hint and exits 1.

✔ Generated icon-16.png
✔ Generated icon-32.png
✔ Generated icon-48.png
✔ Generated icon-128.png

Check whether your config uses any deprecated fields.

Terminal window
extforge upgrade

Loads extforge.config.ts through the current schema. If the load succeeds, reports that the config is up to date. If the load fails (e.g. an old field is no longer valid), reports the error so you can fix it manually.

✔ Your extforge.config is up to date.

Or, on failure:

✖ Config is invalid; fix it before running upgrade.