Setup
Turn on the optional integrations one at a time. Same shape per section so you never get lost.
Quickstart gets the app running with three env vars. This page is where you turn on the optional integrations: OAuth providers, captcha, billing, email, storage, AI, analytics, monitoring, and abuse protection. Every card follows the same shape: a short intro, the steps to do on the provider's side, the env vars to set, and a verification step. Anything you skip stays gracefully degraded in the UI; the kit always boots.
The Setup Doctor
Run pnpm setup:doctor after editing apps/web/.env. It validates the core vars, then prints one line per optional integration showing whether it's enabled, what's missing, and how to fix it.
Core setup
- DATABASE_URLOK
- NEXT_PUBLIC_APP_URLOK
- BETTER_AUTH_SECRETOK
Optional integrations
- Email (log)OK
- GitHub OAuthdisabledMissing GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET
- Google OAuthOK
- Turnstile captchaOK
- Stripe billingOK
- PostHog analyticsOK
- Storage uploadsOK
- Contact formOK
- Abuse protectionOK
Use the doctor as your green-light check after each card below: the line you care about should read OK. See Commands And Scripts for what else the script does.
Optional Integrations
Email + password authentication ships enabled by default. Two-factor auth (TOTP + backup codes) and passkeys (WebAuthn) are wired and ready without any env vars; users opt in from /dashboard/account/security. Expand any integration below to turn it on.
GitHub OAuthOptionalLet users sign in with their GitHub account. Email + password keeps working alongside it.doctor:GitHub OAuth: OK
Create the OAuth app
- Open github.com/settings/developers -> OAuth Apps -> New OAuth App.
- Homepage URL: your
NEXT_PUBLIC_APP_URL(e.g.http://localhost:3000). - Authorization callback URL:
<NEXT_PUBLIC_APP_URL>/api/auth/callback/github. - Click Register application, then Generate a new client secret. Copy both the Client ID and Client Secret.
Set the env vars
| Var | Example |
|---|---|
GITHUB_CLIENT_ID | Iv1.abc123... |
GITHUB_CLIENT_SECRET | ghp_xyz... |
Verify
pnpm setup:doctor reports GitHub OAuth: OK. Visit /auth/sign-in and the GitHub button is active (not greyed out).
For deeper coverage (callback wiring, scopes, adding a new provider), see Authentication.
Google OAuthOptionalLet users sign in with their Google account. Same shape as GitHub; the provider console is different.doctor:Google OAuth: OK
Create the OAuth client
- Open console.cloud.google.com/apis/credentials -> Create Credentials -> OAuth client ID.
- Application type: Web application.
- Authorized JavaScript origins: your
NEXT_PUBLIC_APP_URL. - Authorized redirect URIs:
<NEXT_PUBLIC_APP_URL>/api/auth/callback/google. - Click Create. Copy the Client ID and Client Secret.
Set the env vars
| Var | Example |
|---|---|
GOOGLE_CLIENT_ID | 1234567890-abc.apps.googleusercontent.com |
GOOGLE_CLIENT_SECRET | GOCSPX-... |
Verify
pnpm setup:doctor reports Google OAuth: OK. The Google button is active on /auth/sign-in.
For deeper coverage, see Authentication.
Cloudflare TurnstileOptionalCaptcha protection on sign-up, sign-in, password reset, and the public contact form. Free for any traffic volume.doctor:Turnstile captcha: OK
Create the site widget
- Open dash.cloudflare.com -> Turnstile -> Add site.
- Widget mode: Managed (recommended).
- Domains: your production hostname plus
localhostfor local dev. - Copy the Site Key (public) and Secret Key (server-only).
Set the env vars
| Var | Example |
|---|---|
NEXT_PUBLIC_TURNSTILE_SITE_KEY | 0x4AAAAAAA... |
TURNSTILE_SECRET_KEY | 0x4AAAAAAA... |
Verify
pnpm setup:doctor reports Turnstile captcha: OK. Open /auth/sign-up and the captcha widget renders below the form.
For deeper coverage, see Security: Authentication, Sessions, And Captcha.
Billing (Stripe)OptionalSubscriptions, the customer portal, the dashboard pricing grid, and lifecycle emails. Stripe-hosted checkout; B2B / org-scoped.doctor:Stripe billing: OK
Create the Stripe products
- Sign up at stripe.com; stay in test mode for development.
- Create a Product called "Pro". Add two recurring Prices: monthly and yearly. Copy both price ids.
- In Developers -> API keys, copy your Secret key (
sk_test_*).
Register the webhook endpoint
In the Stripe Dashboard -> Developers -> Webhooks -> Add endpoint:
- URL:
<NEXT_PUBLIC_APP_URL>/api/webhooks/stripe - Events:
checkout.session.completed,customer.subscription.created,customer.subscription.updated,customer.subscription.deleted,customer.subscription.trial_will_end,invoice.finalized,invoice.payment_action_required,invoice.payment_failed,invoice.payment_succeeded.
Copy the signing secret (whsec_*).
For local dev, install the Stripe CLI and run stripe listen --forward-to localhost:3000/api/webhooks/stripe. The CLI prints a whsec_* secret to use locally.
Set the env vars
| Var | Example |
|---|---|
STRIPE_SECRET_KEY | sk_test_51M... |
STRIPE_WEBHOOK_SECRET | whsec_... |
STRIPE_PRICE_ID_PRO_MONTHLY | price_1MxAbc... |
STRIPE_PRICE_ID_PRO_YEARLY | price_1MxDef... |
Verify
pnpm setup:doctor reports Stripe billing: OK. Visit /dashboard/billing; the plan grid renders with the Pro card and a working "Upgrade" button.
For deeper coverage (subscription phases, customizing plans, the webhook story), see Billing.
Email (Plunk)OptionalTransactional email. The default log mode needs zero setup and writes to .local/email-outbox/. Switch to Plunk for production.doctor:Email (plunk): OK
EMAIL_DELIVERY_MODE auto-resolves: plunk if PLUNK_API_KEY is set, noop if NODE_ENV=test, otherwise log. You usually don't need to set it explicitly.
Local development (default)
No setup needed. The kit writes every outgoing email as an .eml file to .local/email-outbox/. Open the file in your mail client to inspect it.
Production (Plunk)
- Sign up at useplunk.com.
- Add and verify your sending domain. Plunk walks you through the SPF, DKIM, and DMARC DNS records.
- Create an API key from Settings -> API.
Set the env vars (production)
| Var | Example |
|---|---|
PLUNK_API_KEY | sk_pk_... |
EMAIL_DELIVERY_MODE | plunk (auto-resolves; only set explicitly to override) |
CONTACT_FORM_TO_EMAIL | hello@your-domain.com (only if the public contact form is enabled) |
Verify
pnpm setup:doctor reports Email (plunk): OK. Trigger a transactional email (sign up a new user; the verification email goes through Plunk's outbound API). The contact form additionally requires CONTACT_FORM_TO_EMAIL plus Turnstile.
For deeper coverage (delivery modes, React Email templates, swapping providers), see Email.
StorageOptionalS3-compatible object storage for avatars, organization logos, and chat image attachments. Pick a provider; the env-var skeleton is the same.doctor:Storage uploads: OK
Native AWS: no custom endpoint, region required. Path-style stays off (default).
AWS_REGION="us-east-1"
AWS_ENDPOINT_URL_S3=""
AWS_S3_FORCE_PATH_STYLE="false"
AWS_ACCESS_KEY_ID="<your-access-key-id>"
AWS_SECRET_ACCESS_KEY="<your-secret-access-key>"
NEXT_PUBLIC_S3_BUCKET_NAME_IMAGES="your-bucket-name"
NEXT_PUBLIC_S3_PUBLIC_URL="https://your-bucket.s3.us-east-1.amazonaws.com"R2 uses an account-scoped endpoint and the magic region auto. Free egress is the usual reason buyers pick it.
AWS_REGION="auto"
AWS_ENDPOINT_URL_S3="https://<account-id>.r2.cloudflarestorage.com"
AWS_S3_FORCE_PATH_STYLE="false"
AWS_ACCESS_KEY_ID="<your-r2-access-key-id>"
AWS_SECRET_ACCESS_KEY="<your-r2-secret-access-key>"
NEXT_PUBLIC_S3_BUCKET_NAME_IMAGES="your-bucket-name"
NEXT_PUBLIC_S3_PUBLIC_URL="https://pub-<bucket-id>.r2.dev"MinIO and most other self-hosted S3 servers expect path-style URLs. Endpoint is your MinIO host.
AWS_REGION="auto"
AWS_ENDPOINT_URL_S3="https://minio.your-domain.com"
AWS_S3_FORCE_PATH_STYLE="true"
AWS_ACCESS_KEY_ID="<your-minio-access-key>"
AWS_SECRET_ACCESS_KEY="<your-minio-secret-key>"
NEXT_PUBLIC_S3_BUCKET_NAME_IMAGES="your-bucket-name"
NEXT_PUBLIC_S3_PUBLIC_URL="https://minio.your-domain.com/your-bucket-name"Don't forget the bucket-side CORS rule (allow PUT from your app origin) and a lifecycle rule expiring tmp/ after 1 day. Without CORS, every upload fails silently. Both rules are copy-pasteable in Storage required bucket configuration.
Verify. pnpm setup:doctor reports Storage uploads: OK. Visit /dashboard/account and upload an avatar; it persists and shows up immediately on the navbar.
For deeper coverage (the presign + finalize pipeline, image validation, what gets stored where), see Storage.
AI (Vercel AI Gateway)OptionalOne key unlocks every supported provider through gateway(modelId). No per-provider keys, no swapping SDKs when a new model lands.
AI_GATEWAY_API_KEY is not yet in apps/web/.env.example. Add it to your .env manually. pnpm setup:doctor doesn't currently track the AI Gateway either; verify by sending a chat message instead.
Create a gateway key
- Sign in at vercel.com.
- Open the AI Gateway page from your dashboard.
- Create a new API key. Copy the value.
Set the env var
| Var | Example |
|---|---|
AI_GATEWAY_API_KEY | vck_... |
Verify
Visit /dashboard/ai-chat, send a message, and confirm tokens stream back. If the request fails at the SDK boundary with an authentication error, the key isn't being read; restart the dev server after editing .env.
For deeper coverage (the streaming chat handler, the ai-elements building blocks, billing gates per model), see AI.
Analytics (PostHog)OptionalProduct analytics, web analytics, session replay, and the foundation that Monitoring builds on.doctor:PostHog analytics: OK
Create a PostHog project
- Sign up at posthog.com.
- Create a project; copy the Project API Key from Project Settings.
- Note your API host (e.g.
https://us.i.posthog.com) and UI host (e.g.https://us.posthog.com). The exact values depend on which region you picked at signup; PostHog shows them on the same settings page.
Set the env vars
| Var | Example |
|---|---|
NEXT_PUBLIC_POSTHOG_KEY | phc_... |
NEXT_PUBLIC_POSTHOG_HOST | https://us.i.posthog.com |
NEXT_PUBLIC_POSTHOG_UI_HOST | https://us.posthog.com |
POSTHOG_PROXY_INGEST_HOST | optional; same as NEXT_PUBLIC_POSTHOG_HOST to enable the same-origin reverse proxy |
POSTHOG_PROXY_ASSET_HOST | optional; pair with POSTHOG_PROXY_INGEST_HOST |
Verify
pnpm setup:doctor reports PostHog analytics: OK. Trigger an action in the app; watch it appear in PostHog -> Live Events within seconds.
For deeper coverage (typed events, identification, session replay, the reverse-proxy story), see Analytics.
Monitoring (PostHog Source Maps and Logs)OptionalBuilds on Analytics. Adds de-minified stack traces in PostHog's Errors view and OpenTelemetry-shaped server logs.
Prerequisite: Analytics enabled. The two env vars below are read at build time only, so changing them requires a fresh build on every host. Set them in your build environment (Vercel, Fly, Render, Docker), not just .env.
Create a personal API key
- In PostHog -> Settings -> Personal API Keys -> Create personal API key.
- Scope: Project: write. Copy the key (
phx_...). - Note your numeric Project ID from Project Settings (e.g.
12345).
Set the env vars
| Var | Example |
|---|---|
POSTHOG_API_KEY | phx_... |
POSTHOG_PROJECT_ID | 12345 |
Verify
Run pnpm build. The build log includes a "Source maps uploaded" line from withPostHogConfig. Trigger an unhandled error in the app; PostHog -> Errors shows the issue with a de-minified stack trace.
For deeper coverage (the three pipelines, structured logging via OTLP, the reverse proxy), see Monitoring.
Abuse Protection (Upstash Redis)Required for productionSliding-window rate limits on auth flows, the contact form, AI chat, and uploads. Required before going to production.doctor:Abuse protection: OK
Create an Upstash Redis database
- Sign up at upstash.com.
- Create a Redis database. Any region is fine; the kit uses the REST API so latency is bounded by HTTPS, not Redis protocol.
- Open the database -> REST API panel. Copy the REST URL and REST Token.
Set the env vars
| Var | Example |
|---|---|
UPSTASH_REDIS_REST_URL | https://us1-active-xxx.upstash.io |
UPSTASH_REDIS_REST_TOKEN | AX... |
Verify
pnpm setup:doctor reports Abuse protection: OK. Send 10 sign-in requests in a row from the same IP; the eleventh returns Too Many Requests.
For deeper coverage (per-surface limits, fail-open vs fail-closed policy, Redis tuning), see Security: Abuse Protection (Upstash).