Environment variables
The one rule: the extension bundle is public
Section titled “The one rule: the extension bundle is public”Everything the extension app reads at build time (any WXT_- or
VITE_-prefixed variable) is compiled into the shipped extension and
readable by anyone who downloads it from the store. Treat extension env as
configuration, never secrets:
- OK in extension env: Firebase web config, hosting URLs, feature flags.
- NEVER in extension env: Stripe secret keys, service-account JSON, webhook signing secrets, any API key that grants data access. Those live only in the backend (Secret Manager / function env).
Extension variables (apps/extension/.env)
Section titled “Extension variables (apps/extension/.env)”Created from .env.example by the setup wizard. All are build-time.
| variable | module | purpose |
|---|---|---|
WXT_GOOGLE_OAUTH_CLIENT_ID |
auth | Google OAuth client ID (Web application type; redirect https://<ext-id>.chromiumapp.org/). Set → sign-in uses the chrome.identity web-auth-flow (+ getAuthToken fast path on Chrome). Empty → offscreen signInWithPopup fallback. |
VITE_FIREBASE_HOSTING_URL |
auth | Firebase Hosting URL used by the offscreen sign-in fallback (https://<project>.firebaseapp.com). |
WXT_ANONYMOUS_AUTH |
auth | true = anonymous-first: every install gets a guest uid immediately; sign-in upgrades it in place (uid preserved). Default false. |
WXT_API_URL |
billing, gate, error-reporting | Deployed Cloud Functions base URL (https://us-central1-<project>.cloudfunctions.net/api). Checkout/portal, gate events, and error reports all post under it. |
VITE_PREMIUM |
billing | true shows premium UI (pricing, portal, status). Plans/copy live in site.config.ts; amounts and trials are server-side. |
WXT_DEMO_SURFACES |
demo-newtab, demo-devtools | true builds the optional demo entrypoints (branded new tab + devtools panel). Default false so the standard build never takes over the user’s new tab. |
The Firebase web config itself is not env; paste it into
apps/extension/utils/firebase.ts (the TODO marker).
File layering (WXT/Vite dotenv order)
Section titled “File layering (WXT/Vite dotenv order)”Loaded from apps/extension/; later files override earlier ones:
.env: base values (untracked; created from.env.example).env.local: personal overrides (untracked).env.[mode]: per-mode, e.g..env.development(trackable).env.[mode].local: personal per-mode overrides (untracked)
For browser-specific values, prefer branching on
import.meta.env.BROWSER (or per-browser manifest fields in
wxt.config.ts) over separate .env.chrome/.env.firefox files.
Prefixes
Section titled “Prefixes”WXT_*: preferred for new variables (exposed onimport.meta.env).VITE_*: also exposed; parts of the kit still use it.- Unprefixed variables are not available to app code; use that deliberately for build-machine-only values.
Keep .env.example exhaustive, and keep each module’s module.json env
list in sync; that’s what lets the pruner remove template entries with
their module.
Server-side (backend/functions)
Section titled “Server-side (backend/functions)”Secrets, set once per project:
firebase functions:secrets:set STRIPE_SECRET_KEY # sk_…firebase functions:secrets:set STRIPE_WEBHOOK_SECRET # whsec_… (pnpm stripe:webhook sets this for you)Non-secret knobs, plain env on the function:
| variable | purpose |
|---|---|
BILLING_SUCCESS_URL |
checkout success return page (https) |
BILLING_CANCEL_URL |
checkout cancel return page (https) |
BILLING_PORTAL_RETURN_URL |
customer-portal return URL |
BILLING_TRIAL_DAYS |
card-free trial length; 0 = none. Display copy in site.config.ts → pricing.trialDays should match; the server stays the authority |
BILLING_AUTOMATIC_TAX |
"true" enables Stripe Tax on checkout |
BILLING_ALLOW_PROMO_CODES |
"false" hides the promo-code field (default on) |
Credit-based billing needs no env of its own: credit amounts live in
Stripe price metadata (credits on packs, monthly_credits on the metered
plan; pnpm seed:stripe sets them), and site.config.ts → pricing.plans
decides which model is sold. See
the credits model.
For local emulator runs, the same two secrets go in
backend/functions/.secret.local (gitignored); see the
payments guide.
