This is the full developer documentation for ExtensionStart # Introduction > ExtensionStart documentation: from clone to a published, monetized browser extension. ExtensionStart is a starter kit for paid browser extensions: a monorepo with sign-in, Stripe billing, paywalls, and store publishing wired up and tested. These docs walk the same path you will: from clone to a published, monetized extension on Chrome, Firefox, and Edge. New here? Read the [Overview](/docs/getting-started/what-you-get/) to see what is in the box, then follow the five setup steps in order. ## Popular pages [Section titled “Popular pages”](#popular-pages) [Setup](/getting-started/quickstart/)Clone, run the wizard, load the extension in Chrome. About 10 minutes. [Take payments](/getting-started/take-payments/)Stripe in test mode, ending with a checkout that flips your popup to premium. [Paywalls and free limits](/guides/gates/)Pick a preset, list your premium features, stay inside store policy. [Publish to the Chrome Web Store](/publishing/first-submission/)Developer account, listing, privacy tab, and what review expects. ## How the docs are organized [Section titled “How the docs are organized”](#how-the-docs-are-organized) * **Getting started**: the five-step path from clone to store listing. Follow it in order; each step ends with something you can verify. * **Working with the codebase**: one guide per subsystem. Sign-in, payments, credits, paywalls, on-page UI, theming, announcements, the background service worker, and the module system. * **Going to production**: submission runbooks and policy references for all three stores. * **Reference**: CLI flags, environment variables, and the messaging protocol. ## Give the docs to your AI agent [Section titled “Give the docs to your AI agent”](#give-the-docs-to-your-ai-agent) * [extensionstart-docs.md](/docs/downloads/extensionstart-docs.md): every page in one Markdown file, sized for a context window. * [llms.txt](/docs/llms.txt) · [llms-full.txt](/docs/llms-full.txt): machine-readable indexes. * Append `.md` to any page URL for raw Markdown, or use the “Copy as Markdown” button next to each page title. The [AI agents guide](/docs/guides/ai-agents/) covers the prompt recipes and Chrome DevTools MCP preset that ship in the repo. ## FAQ [Section titled “FAQ”](#faq) ### Do I have to use Firebase? [Section titled “Do I have to use Firebase?”](#do-i-have-to-use-firebase) Firebase (Auth, Firestore, Cloud Functions) is the supported, wired-up path, and its free tier covers development. The billing core is port-based, so adapting it to another backend is a contained task rather than a rewrite. ### Which browsers are supported? [Section titled “Which browsers are supported?”](#which-browsers-are-supported) Chrome and Edge (MV3), plus Firefox. The build produces per-browser bundles, Chromium-only APIs are feature-guarded, and CI lints the Firefox build on every commit. ### How does billing work under the hood? [Section titled “How does billing work under the hood?”](#how-does-billing-work-under-the-hood) Stripe Checkout and the customer portal, driven by one Cloud Function. Webhooks write entitlements server-side; the extension reads a storage-backed snapshot. The server is the authority. ### Can I charge subscriptions, one-time payments, or credits? [Section titled “Can I charge subscriptions, one-time payments, or credits?”](#can-i-charge-subscriptions-one-time-payments-or-credits) All of them. Four monetization models ship ready to seed into Stripe; the wizard configures the one you pick. See [Payments](/docs/guides/billing/) and [Credits](/docs/guides/billing-credits/). ### What if I get stuck? [Section titled “What if I get stuck?”](#what-if-i-get-stuck) Every subsystem has its own guide in the sidebar. For anything else, email . # Connect sign-in > Step 2 of 5. Point the kit at your own Firebase project and sign in for real. This guide connects the kit to your own Firebase project so Google and email sign-in work for real. Each step is one command or one console screen; the [Sign-in guide](/docs/guides/auth/) owns the detail. ## The checklist [Section titled “The checklist”](#the-checklist) 1. **Install the Firebase CLI and log in:** `npm i -g firebase-tools`, then `firebase login`. 2. **Run the Firebase setup:** ```sh pnpm create extstart --firebase ``` It creates or picks a project, writes the SDK config into every file that carries it, and prints deep links for the console steps below ([what it writes](/docs/guides/auth/#the-automated-path-recommended)). 3. **Enable the sign-in providers:** Google and Email/Password (and Anonymous, if you use anonymous-first) in the console screen the wizard linked. 4. **Create the Google OAuth client** and paste its ID as `WXT_GOOGLE_OAUTH_CLIENT_ID` when the wizard offers ([redirect-URI steps](/docs/guides/auth/#the-google-oauth-client-web-auth-flow-path)). 5. **Upgrade the project to the Blaze plan.** Step 3 deploys a Cloud Function, which the free Spark plan can’t do. The free-tier quota covers development. 6. **Rebuild and reload:** restart `pnpm dev` (env values are baked in at build time), then reload the extension on `chrome://extensions`. ## Verify [Section titled “Verify”](#verify) Open the popup and click **Sign in with Google**. The “Connect your Firebase project” notice is gone, the OAuth window completes, and your account shows in the Account tab. If the sign-in window opens and closes without signing you in, the OAuth redirect URI doesn’t match your current extension ID; see [the fix](/docs/guides/auth/#the-google-oauth-client-web-auth-flow-path). To skip the OAuth client entirely, use the [offscreen popup path](/docs/guides/auth/#the-offscreen-popup-path) instead (Chromium only, zero OAuth config). Next: [3. Take payments](/docs/getting-started/take-payments/). # Build your first feature > Step 4 of 5. Where your code goes, and the 2-file edit that makes a feature paid. This guide shows you where your code goes and the 2-file edit that makes a feature paid. Build here any time; nothing before this step depends on it. ## Where your code goes [Section titled “Where your code goes”](#where-your-code-goes) The popup renders a card titled **“Your feature goes here”**. That is `apps/extension/components/YourFeature.tsx`, a small commented component that is yours to gut: * Rename it freely (update the import in `apps/extension/entrypoints/popup/main.tsx`). * Replace its two buttons with your real UI. Keep the primitives from `@extensionstart/ui` and the token color scales; both themes come free. * Talk to the background only through `sendMessage` from `@/utils/messaging`. It is typed, so wrong payloads won’t compile. Unlike the demos, `YourFeature.tsx` is core: the wizard never prunes it. ## Count free actions [Section titled “Count free actions”](#count-free-actions) The default `value-first` preset raises the paywall on the 10th recorded action. Record one after your feature does its work: ```ts sendMessage("gateAction", { name: "your-free-action" }).catch(console.error); ``` This is the free button in `YourFeature.tsx`, verbatim. ## Make a feature premium: the 2-file edit [Section titled “Make a feature premium: the 2-file edit”](#make-a-feature-premium-the-2-file-edit) **File 1: `apps/extension/entrypoints/background/gates.ts`.** Add your feature ID to the list at the top: ```ts export const PREMIUM_FEATURES = ["premium-demo", "export-pdf"]; ``` **File 2: your call site.** Ask the gate engine before running the feature: ```ts const decision = await sendMessage("gateFeature", { feature: "export-pdf" }); if (decision !== null) return; // the wall is already rendering; stop // …run the premium feature… ``` The premium button in `YourFeature.tsx` does exactly this with the `"premium-demo"` ID, so you can compare against a working example. ## What the engine handles for you [Section titled “What the engine handles for you”](#what-the-engine-handles-for-you) * **The wall renders itself** in the popup, sidepanel, and content-script surfaces; you never build wall UI. * **Dismissals cool down.** A dismissed paywall stays quiet for 24 hours. Every timing number lives in `gates.ts`. * **Sign-in chains.** Signed-out users see the sign-in wall first, then the paywall. Sign-in never fires standalone, which keeps you inside [store policy](/docs/guides/gates/#stay-inside-chrome-web-store-policy). * **Usage mirrors to the server.** Events flush to `POST /gate/events` every minute; anything with stakes reads server counters, not client ones. ## Delete the demos when ready [Section titled “Delete the demos when ready”](#delete-the-demos-when-ready) * **GateDemo** (`apps/extension/components/GateDemo.tsx`): once your own feature calls `gateFeature`, delete the file and its two `GateDemo` lines in `entrypoints/popup/main.tsx`. Don’t prune the `gate` module to remove it; that would delete your paywall engine too. * **Highlighter**: the content-script demo is the `content-demo` module; rerun `pnpm create extstart` or see the [module system](/docs/guides/modules/). Next: [5. Ship it](/docs/publishing/first-submission/). For presets, timing knobs, and manual wall control, see [Paywalls](/docs/guides/gates/). # Repo tour > The monorepo map: apps, packages, backend, and how they fit together. ExtensionStart is a pnpm + Turborepo monorepo. Here’s the map, top to bottom. ## `apps/extension`: the extension app [Section titled “apps/extension: the extension app”](#appsextension-the-extension-app) WXT + React 19 + Tailwind 4 + Firebase. WXT **generates** `manifest.json` from `wxt.config.ts` per browser; never hand-edit a manifest. * `entrypoints/background/`: one module per concern, imported by `index.ts` in a fixed order: `migrations` → `errors` → `logs` → `firebase` (auth) → `billing` → `gates` → `broadcasts` → `update-notice`. All event listeners are registered at the top level ([why that matters](/docs/guides/background/)). * Surfaces: `popup` and `sidepanel` mount the same account/settings tabs (no separate options page); `welcome` is the first-run tour; `content` holds the shadow-DOM UI: status bar, gate walls, highlighter demo; `offscreen` is the auth fallback. Demo surfaces `newtab` and `devtools` build only with `WXT_DEMO_SURFACES=true`. * `utils/`: the typed messaging protocol, settings store, shadow-UI mount, DOM observer, highlight persistence, error reporting, page bridge. * `hooks/`: `useAuth`, `useBilling`, `useTheme`. * `site.config.ts`: every user-facing name, email, URL, and pricing-copy string. Rebranding starts (and mostly ends) here. ## `packages/`: framework-free cores [Section titled “packages/: framework-free cores”](#packages-framework-free-cores) Each package is a framework-free core with a thin React subpath (`/react`) where UI bindings exist. Lint enforces importing only public entrypoints. | package | what it is | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `core-ext` | `defineStore` / `defineStorageView` / `defineMessaging` / `defineProxyService` / `defineAlarm` / `defineMigrations` (the MV3 survival kit) plus a `/testing` chrome mock | | `core-auth` | auth strategies (web-auth-flow default, offscreen fallback), anonymous-first linking, typed auth errors | | `core-billing` | entitlement snapshot + states matrix, billing API client, storage-backed hooks (`useEntitlement('paid')`) | | `gate` | trigger primitives and combinators, the gate evaluator (cooldowns/escalation/chaining), presets, and the `GateWall` UI (`/react`) | | `ui` | Button/Card/Input/Badge/Skeleton/Dialog/Toast (CVA variants over Base UI) | ## `backend/functions`: one Hono app [Section titled “backend/functions: one Hono app”](#backendfunctions-one-hono-app) A single Hono app on Cloud Functions v2, deliberately self-contained: Firebase packs it standalone, so it can’t import workspace TypeScript. Routes: `/gateConfig`, `/billing/checkout`, `/billing/portal`, `/billing/webhook`, `/gate/events`, `/auth/revoke`, `/errors`. The billing core is port-based (StripeGateway / EntitlementStore / ClaimsWriter) so alternative payment providers can implement the same contract. ## `tooling/` [Section titled “tooling/”](#tooling) * `create/`: the `create-extstart` wizard ([reference](/docs/reference/cli/)). * `config/`: shared ESLint/Prettier/tsconfig presets and the `module.schema.json` that validates module manifests. ## `module.json` everywhere [Section titled “module.json everywhere”](#modulejson-everywhere) Every prunable feature module carries a `module.json` manifest declaring its files, dependencies, env vars, and manifest permissions. The wizard’s pruner consumes these: dropping a module removes its code, deps, env entries, and permissions together. Details in the [module system guide](/docs/guides/modules/). ## Commands you’ll use [Section titled “Commands you’ll use”](#commands-youll-use) From the repo root: ```sh pnpm build # build everything pnpm typecheck # TypeScript, strict, workspace-wide pnpm lint # ESLint, workspace-wide pnpm turbo run test # unit tests ``` Extension-specific root aliases (the long form `pnpm --filter @extensionstart/extension