Skip to content
Get the kit

Setup wizard

pnpm create extstart configures your clone in place. It asks a few questions, applies the answers to the repo, creates your Firebase or Supabase project, and sets up payments. It downloads nothing and touches no file outside the clone. Run it from anywhere inside the clone:

Terminal window
pnpm create extstart

The command resolves to the wizard published on npm, which hands over to the copy that ships in your clone, so the prompts always match the kit version you cloned. Menus take the arrow keys; Enter accepts the highlighted default.

  • Node 22.12 or newer. The clone’s .nvmrc names Node 22, so nvm install picks up the latest 22 release. The wizard checks first and stops on an older Node.
  • A clean git tree. The wizard deletes and rewrites files, so it refuses to run with uncommitted changes; every change is then one git diff away from review and one git checkout . away from undo.
  • For Firebase, the CLI installed and logged in; for Supabase, supabase login done or a personal access token at hand (Setup, step 3), so the wizard can create the project.
  • Your payment provider’s test key, for the payments step.
  1. Extension name and One-line description. Written to apps/extension/site.config.ts, which the manifest and every surface read. Change them there any time. The same file holds your support email and the links behind “Help & support”, the privacy policy, and the welcome page; on Firebase the Firebase step points the links at your deployed website. The name also goes on your website (apps/site/src/site.ts, with the description as its hero line and meta description) and on the checkout return and uninstall pages; the rest of the website’s copy describes a sample product until you write yours, and the wizard’s Next list says so while it does.
  2. Color palette: the brand colors of the extension and the website. ember (the default: bright orange), blue, violet, teal, pink, or mono (black and white), plus any palette of your own in packages/ui/palettes/. It points the extension and the website at the palette and writes its colors into the checkout return and uninstall pages and the store asset templates. Palettes shows what each one covers and how to make your own.
  3. Backend: firebase (the default) or supabase. Both run the same API routes and the same security model. The wizard keeps the stack you pick and prunes the other one from your clone. It prints one line under your answer: on Firebase, the CLI version and your account, or what to install; on Supabase, whether an access token was found.
  4. Side panel: no (the default) ships the popup only. yes also docks the popup’s tabs in Chrome’s side panel. The popup and the side panel.
  5. Sign-in: when-needed (the default) opens the popup as a guest and asks for an account in front of checkout and on features that need one. first-screen shows the sign-in form first. When sign-in is asked.
  6. Billing model: what customers buy. subscription (the default: monthly, yearly, lifetime), lifetime-only, hybrid-credits (a subscription with monthly credits, plus top-up packs), credits-only, or free. A model writes the plans in site.config.ts and makes the matching paywall recipe the next default. free removes billing, the paywall and the on-page content script together, and skips the next two questions. Choose a billing model.
  7. Payment provider: stripe (the default), polar, or paddle. Your clone keeps the provider you pick, and Stripe always: the wizard removes the other providers’ code, so you only read the one you use. Polar and Paddle are merchant of record and handle sales tax. What differs.
  8. Paywall recipe: what stays free, and when the wall shows. freemium (the default), free-trial, daily-limit, credits, paid-only, or manual. It writes apps/extension/paywall.config.ts; the numbers (free uses, trial days, when to ask) live in that file and change without a rerun. Choose your paywall previews each one.
  9. On-page UI: no (the default on a fresh clone) or yes. The kit’s on-page demo is a content script on every https:// page that shows the status card and the page’s walls. It is the extension’s only access to websites, which the store shows at install as “Read and change all your data on all websites”. no removes it, its card and its tests; asked only when billing stays. On-page UI.

On a clone the wizard has already configured, every question defaults to what the clone says now (its palette, side panel, sign-in, plans, provider, recipe, and on-page demo), so Enter changes nothing. A config you edited by hand is offered as-is, which keeps it. The payment provider menu then lists Stripe and the provider your clone kept; to move to one the wizard removed, see Switch providers.

The wizard then applies your answers. The backend stack you did not pick goes, and so do the payment providers you did not pick, billing (on free), the side panel (on no), and the on-page demo (on no): their files are deleted, their env vars and permissions removed, and apps/extension/.env is created from the pruned template (how pruning works). It also writes apps/extension/extension-public-key.txt, which fixes your extension’s ID so every clone and teammate gets the same one. Commit that file. Then it regenerates the WXT types and typechecks the pruned tree (--with-tests adds the unit suites).

Each of these steps, and each step below, is one line in the terminal: a spinner with the seconds so far while it runs, then a check mark. A step that fails prints what its command said. --verbose streams every command instead.

Create the Firebase project now? Enter runs the step. The question appears only when the Firebase CLI is installed and logged in; otherwise the Next list at the end says what to install.

  • Firebase project: create (the default) asks for a Project id (lowercase letters, digits, hyphens, 6 to 30 characters) and a Display name, then creates the project, about 30 seconds. existing lists your projects to pick from.

The wizard writes the project’s config into the extension and the backend, creates the Firestore database, deploys the kit’s Firestore rules, writes the example broadcast (broadcasts/welcome, the banner the popup shows; what it is), and switches on Email/Password sign-in. It deploys your website to the project’s Hosting (https://<project-id>.web.app: the site template with its help center and privacy policy) and points the extension’s “Help & support”, privacy link, and welcome page at it, unless site.config.ts already has your own. Then it prints what only the console can do. A project minutes old can refuse the rules for a while; the payments step’s deploy ships them then.

Three steps, each with a link into the right screen, listed only while they are still open. Do them in this order; the first one creates what the third one edits.

  1. Enable sign-in providers. Firebase console → Authentication → Sign-in method (click Get started if the page shows it instead of the provider list). Open Google, switch Enable on, pick a Project support email, Save. Firebase creates the OAuth consent screen and a web OAuth client at this point. If the checklist also lists Email/Password or Anonymous, enable them on the same page. Leave “Email link (passwordless)” off; the kit does not use it.
  2. Upgrade to the Blaze plan. The API runs on Cloud Functions, which the free Spark plan cannot deploy. Bottom of the console’s left sidebar → Spark plan → Upgrade → Blaze, then pick or create a Cloud Billing account; Google asks for a card here. Set a budget alert when offered, for example $5 a month; it emails you and cuts nothing off. The Blaze free tier is the same as Spark’s, so development costs nothing.
  3. Point the Google OAuth client at your extension. This is what “Continue with Google” opens, and it needs your extension’s redirect URI, which the wizard prints in full: https://<extension-id>.chromiumapp.org/. Google Cloud console → APIs & Services → Credentials → Web client (auto created by Google Service) → Authorized redirect URIs → Add URI, paste it with the trailing slash, Save. No web client there? Create credentials → OAuth client ID, type Web application, the same redirect URI. While the consent screen is in Testing, only the test users listed there can sign in: add yourself now, and publish the app before the store release. The kit asks only for the basic profile scopes, so Google needs no verification review.

After the checklist the wizard asks: Paste your Google OAuth client id now (Enter to skip). The Client ID ends in .apps.googleusercontent.com and sits at the top of the web client’s page. Paste it and the wizard writes it to apps/extension/.env as WXT_GOOGLE_OAUTH_CLIENT_ID. You never need the client secret. Skipped it? Once Google sign-in is on, a rerun reads the client ID from your project by itself:

Terminal window
pnpm create extstart --firebase

The rerun is safe on a configured clone, needs no clean tree, and lists only the console steps still open. It updates the Firebase lines of .env and leaves the rest in place. Restart pnpm dev afterwards; env values are baked in at build time.

The offscreen sign-in path that needs no OAuth client, the manual path without the wizard, and the fix when Google answers “Access blocked”: Authentication guide.

Ask your agent

Paste into Claude Code or Cursor from the repo root:

Enable Google and Email/Password sign-in for my Firebase project, add the redirect URI https://<extension-id>.chromiumapp.org/ to the web client Firebase created (or create a Web application OAuth client if there is none; pnpm create extstart --firebase writes apps/extension/extension-public-key.txt, which fixes the extension ID, and prints the exact redirect URI), and put its client ID in apps/extension/.env as WXT_GOOGLE_OAUTH_CLIENT_ID. Tell me which console screens you cannot reach so I do those myself.

Create the Supabase project now? Enter runs the step. It needs an access token: the wizard finds the one supabase login stored, or SUPABASE_ACCESS_TOKEN, and otherwise asks you to paste one from your account’s tokens page. The token stays in memory for the run.

  • Supabase project: create (the default) asks for the Organization (only when you have several), the Region, and a Project name, then creates the project on your organization’s plan and waits until it is up, a minute or two. The database password is generated and never printed: the kit never connects to Postgres directly, and the project’s database settings in the dashboard reset it when you need one. existing lists your projects to pick from.

The wizard writes the project URL, the publishable key, and the API URL into apps/extension/.env, applies the kit’s migrations through the same history supabase db push uses, deploys the Edge Function, and switches on the auth settings the extension needs: email sign-in without a confirmation mail, a password reset email with a code the extension takes (unless your template already shows one), manual linking, anonymous sign-ins when the extension runs anonymous-first, and the extension’s redirect URL. The migration also inserts the example broadcast the popup shows (what it is). Then it prints what is left.

One step, and it is outside Supabase: Google sign-in needs an OAuth client that only Google Cloud can create.

  1. Create a Google OAuth client. Google Cloud console → APIs & Services → Credentials → Create credentials → OAuth client ID, type Web application. Authorized redirect URI: https://<project-ref>.supabase.co/auth/v1/callback, which the wizard prints in full. While the consent screen is in Testing, only the test users listed there can sign in: add yourself now.

After the checklist the wizard asks: Paste your Google OAuth client id now (Enter to skip), then the Client secret. It switches Google on in your project with them; the secret goes to Supabase, never into a file. Skipped it? Rerun the step:

Terminal window
pnpm create extstart --supabase

The rerun is safe on a configured clone, needs no clean tree, skips the migrations already applied, redeploys the function, and lists only the steps still open. Two more items appear only when an API call did not go through: copying the publishable key into .env by hand, and adding the redirect URL under Authentication → URL Configuration. The manual path without the wizard: Supabase guide.

Set up payments with stripe now? (or your provider). Asked as soon as the Firebase or Supabase project is wired in and billing is kept. Enter runs the step; n keeps it as one command for later:

Terminal window
pnpm create extstart --payments-setup

Run on its own, it first asks for the provider among the ones your clone ships, the one the clone is set to first (--payments answers it; a clone that ships only Stripe skips the question). What it does on Firebase, in order:

  1. Checks the Blaze plan. Not on it yet? It prints the upgrade link and waits: Upgraded to Blaze? Yes checks again. The console can take a moment to show the upgrade.
  2. Polar and Paddle only: Use the sandbox? Enter keeps the test environment (POLAR_SANDBOX or PADDLE_SANDBOX in the backend env).
  3. Asks for the key, without echoing it, and confirms the paste with its prefix and last four characters (sk_test_…F456).
    • Stripe: the Secret key from the API keys page, sk_test_….
    • Polar: an Organization Access Token from the sandbox dashboard, under Settings, polar_oat_….
    • Paddle: an API key from Developer tools → Authentication, pdl_…_apikey_….
  4. Stores the key and the settings: the key in Secret Manager (it never lands in a file), and backend/functions/.env with the return URLs from your project id, the provider, the sandbox flag, and the card-trial length from paywall.config.ts (BILLING_TRIAL_DAYS: checkout offers a trial only while it is above 0).
  5. Creates the products in your provider, by the kit’s lookup keys.
  6. Registers the webhook. Stripe: by API, secret stored. Polar and Paddle: the wizard prints the webhook URL for you to add in the dashboard, then asks for the endpoint secret.
  7. Deploys the API and the Firestore rules, a few minutes. Hosting (your website and the hosted pages) goes with it unless the Firebase step of the same run just deployed it.
  8. Runs the doctor, which checks the whole chain and names the fix for anything red.

A missing key, or a Blaze upgrade you keep for later, stops the step with the command to re-run; nothing is half done. The accounts to create first and the manual route, per provider: Stripe, Polar, Paddle.

On Supabase the same step runs against your project with the access token from the project step: no Blaze check, and the only file it writes is the provider line in apps/extension/.env. The key, the provider, the sandbox flag, and the card-trial length from paywall.config.ts are stored as function secrets, the products are seeded, the webhook is registered (Stripe by API, Polar and Paddle by paste), and the step checks that the API answers. Nothing is deployed: the function reads its secrets on the next request, and it serves the checkout return pages itself at /functions/v1/api/return/…, so no website is needed. The manual route: Supabase guide.

The wizard prints Next: pnpm dev with the load-unpacked path, and one line for each setup step you skipped, as the command that runs it and a link here. A step that stopped short says what is missing first: a Firestore database that could not be created, Supabase migrations that did not apply, or an Edge Function that did not deploy. The payments step is offered only once the project step has finished. Continue with Setup, step 5.

AnswerRemoves
Backend firebasethe Supabase stack
Backend supabasethe Firebase stack, and the uninstall survey, whose page and route live there
Side panel nothe side panel page and the sidePanel permission
Billing model freecheckout, plans, entitlements, the paywall, the on-page demo, and their permissions
On-page UI nothe on-page demo: the content script, the status card and their e2e tests, so the extension asks for no access to websites. It carries no wiring markers, so a later run removes it too: pnpm create extstart --on-page no --yes
Payment providerthe providers you did not pick, adapter and seed (Stripe always stays)

Everything else always ships: auth, the UI kit, your website (apps/site), and broadcasts. The pruner leaves billing’s own backend/** files in place when billing goes; the backend is one self-contained function and unused routes are harmless. The Next list then names BILLING_DISABLED=true, which lets the API deploy without payment secrets and turns its billing routes off.

Terminal window
pnpm create extstart --yes --side-panel no --name "My Ext"

--yes accepts the defaults with no prompts. It keeps billing, the side panel and the on-page demo unless --billing-model free, --side-panel no or --on-page no says otherwise, and of the payment providers keeps Stripe and the one --payments names (else the one the clone is set to). --dry-run prints the prune plan, every file included, and changes nothing. A headless run never creates cloud resources without --firebase-create or --supabase-create, and the payments step reads the key from the environment. Every flag: CLI reference.

The wizard is built for one configuration pass on a fresh clone. To experiment, run it, inspect git diff, and git reset --hard to try a different combination. A second pass starts from your clone’s answers and rewrites only what you change. After the first run the module:* wiring markers are stripped from the kept files, so a second pass can no longer remove billing, the side panel, or the payment provider you kept: it skips the side panel question and leaves free off the billing menu. Run the first pass with --keep-markers to keep that open. The Firebase, Supabase, and payments steps rerun any time on their own: --firebase, --supabase, and --payments-setup.

A recipe rewrites trial, prompt, and the two demo features (your-free-action, premium-demo). Once you have renamed those to your own features, a rerun sets trial and prompt only, and says so; give your features the recipe’s values in paywall.config.ts, or run /change-paywall-recipe in Claude Code.