Skip to content
Overview

Setup

After Quickstart, configure the integrations your app needs and verify each one with the setup doctor.

Last updated on

9 min read

The Setup Doctor

Run pnpm setup:doctor after editing apps/web/.env. It checks the three core variables and reports which optional integrations are ready.

$ pnpm setup:doctor

Core setup

  • DATABASE_URLOK
  • NEXT_PUBLIC_APP_URLOK
  • BETTER_AUTH_SECRETOK

Optional integrations

  • GitHub OAuthdisabledMissing GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET
  • Stripe billingOK

Run it after each integration and confirm that integration reads OK. See Commands And Scripts for the script's other checks.

Optional Integrations

Email and password authentication works without additional provider configuration. Add the integrations your product needs. A production deployment requires an email delivery provider and Upstash abuse protection. Expand one section, configure it, then run pnpm setup:doctor.

NEXT_PUBLIC_APP_URL is an origin only: a scheme, hostname, and optional port, with no path or trailing slash. Use http://localhost:3000 locally and https://example.com in production. Provider callback paths are appended to this origin by default. If you set BETTER_AUTH_URL or use the kit's OAuth proxy, follow Authentication: Configuring OAuth Providers.

GitHub logoGitHub OAuthLet users sign in with their GitHub account. Email + password keeps working alongside it.

Create the GitHub OAuth Apps

The fields below belong in GitHub's OAuth App form; they are not environment variables. Open GitHub Developer Settings, choose OAuth Apps, then New OAuth App.

GitHub OAuth Apps support one Authorization callback URL. For a direct setup, create one OAuth App for local development and another for production.

GitHub fieldDevelopment appProduction app
Homepage URLhttp://localhost:3000https://example.com
Authorization callback URLhttp://localhost:3000/api/auth/callback/githubhttps://example.com/api/auth/callback/github

Register both applications, generate a client secret for each, and copy both Client ID and Client Secret pairs.

Set the env vars

Put the development app's values in local apps/web/.env. Put the production app's values in your production host. Each environment's NEXT_PUBLIC_APP_URL must match that environment's origin.

VarDevelopmentProduction
GITHUB_CLIENT_IDDevelopment app Client IDProduction app Client ID
GITHUB_CLIENT_SECRETgenerated-by-githubgenerated-by-github
NEXT_PUBLIC_APP_URLhttp://localhost:3000https://example.com

The kit's OAuth proxy is an advanced alternative when previews need to share the production callback. See Authentication: Configuring OAuth Providers.

Verify

pnpm setup:doctor reports GitHub OAuth: OK. Visit /auth/login and confirm the GitHub button is enabled.

For deeper coverage (callback wiring, scopes, adding a new provider), see Authentication.

Google logoGoogle OAuthLet users sign in with Google using one web client for local and production environments.

Create the OAuth client

Open Google Cloud Credentials, choose Create Credentials, then OAuth client ID. Select Web application.

One Web application OAuth client can contain both local and production values:

Google fieldDevelopmentProduction
Authorized JavaScript originshttp://localhost:3000https://example.com
Authorized redirect URIshttp://localhost:3000/api/auth/callback/googlehttps://example.com/api/auth/callback/google

Create the client, then copy its Client ID and Client Secret.

Set the env vars

VarExample
GOOGLE_CLIENT_ID1234567890-abc.apps.googleusercontent.com
GOOGLE_CLIENT_SECRETGOCSPX-...

The same client values can be used locally and in production. Set each environment's NEXT_PUBLIC_APP_URL to its own origin.

Verify

pnpm setup:doctor reports Google OAuth: OK. Visit /auth/login and confirm the Google button is enabled.

For deeper coverage, see Authentication.

Cloudflare logoCloudflare TurnstileAdd challenge-based bot protection to authentication and the public contact form.

Create the site widget

Open Cloudflare Dashboard, choose Turnstile, then Add site. Use Managed widget mode.

Domains accepts hostnames only. Add localhost and example.com, without a scheme or path. Copy the Site Key and server-only Secret Key.

Set the env vars

VarExample
NEXT_PUBLIC_TURNSTILE_SITE_KEY0x4AAAAAAA...
TURNSTILE_SECRET_KEY0x4AAAAAAA...

Verify

pnpm setup:doctor reports Turnstile captcha: OK. Complete a sign-up at /auth/signup or submit the configured contact form. The widget uses interaction-only mode and can remain invisible unless Cloudflare requests a challenge, so a visible widget is not required for verification.

For deeper coverage, see Security: Authentication, Sessions, And Captcha.

Stripe logoStripe billingEnable organization billing with the Free, Pro, and Ultra catalog, Stripe Checkout, and the customer portal.

Create the Stripe catalog

Sign up at Stripe and build the initial catalog in test mode for development. The kit's catalog contains Free, Pro, and Ultra. Free is the baseline and does not need a Stripe price.

Create Pro and Ultra products. Add recurring monthly and yearly prices to each product, then copy all four price_* IDs. Open Developers, choose API keys, and copy the Secret key (sk_test_*).

Register the deployed webhook

In the Stripe Dashboard, use the same mode as the environment you are configuring. Open Developers, choose Webhooks, then Add endpoint. Set the deployed endpoint URL to:

https://example.com/api/webhooks/stripe

Subscribe to the events in the Billing webhook dispatch table, then copy this endpoint's signing secret (whsec_*) into that environment.

Before launch, switch Stripe to live mode and repeat the Pro and Ultra products, all four prices, and the Dashboard webhook. The Dashboard endpoint, STRIPE_SECRET_KEY, price IDs, and STRIPE_WEBHOOK_SECRET must all come from the same Stripe mode and environment. Follow Going To Production: Pricing And Stripe Live Mode for the cutover.

Forward webhooks locally

Install the Stripe CLI, authenticate it, then run:

stripe listen --forward-to localhost:3000/api/webhooks/stripe

The CLI prints a local whsec_* for that listener session. Use it in local apps/web/.env. A deployed Dashboard endpoint has its own mode-specific whsec_*; do not reuse either secret in the other environment.

Set the env vars

VarExample
STRIPE_SECRET_KEYsk_test_51M...
STRIPE_WEBHOOK_SECRETwhsec_...
STRIPE_PRICE_ID_PRO_MONTHLYprice_1MxAbc...
STRIPE_PRICE_ID_PRO_YEARLYprice_1MxDef...
STRIPE_PRICE_ID_ULTRA_MONTHLYprice_1MxGhi...
STRIPE_PRICE_ID_ULTRA_YEARLYprice_1MxJkl...

Use test-mode keys, prices, and the Stripe CLI webhook secret locally. Use live-mode keys, prices, and the Dashboard webhook secret in production.

Verify

pnpm setup:doctor reports Stripe billing: OK. Visit /dashboard/billing, confirm the Free, Pro, and Ultra plans render, and use a paid plan's Get Started button to open Stripe Checkout.

For deeper coverage (subscription phases, customizing plans, the webhook story), see Billing.

Email deliveryRequired for productionInspect transactional email locally in log mode, then connect a delivery provider for production.

When EMAIL_DELIVERY_MODE is unset, the kit selects the single configured provider credential. With no provider it uses noop in tests and log otherwise. If you configure more than one provider, set EMAIL_DELIVERY_MODE explicitly.

Use local log mode

No provider setup is needed. The kit writes every outgoing email as an .html file in .local/email-outbox/; open it in a browser to inspect the rendered message.

Choose a production provider

Production supports Plunk, Resend, Postmark, Brevo, SendGrid, and SMTP. This walkthrough uses Plunk. See Email for the other provider modes.

Configure Plunk

Sign up at Plunk, add and verify your sending domain, then create an API key in Settings, under API.

Set the env vars (production)

VarStatusExample
PLUNK_API_KEYRequiredsk_pk_...
EMAIL_FROMRequiredSyntaxKit <noreply@example.com>
EMAIL_DELIVERY_MODEOptional explicit overrideplunk
CONTACT_FORM_TO_EMAILOptional; public contact form onlyhello@example.com

Plunk is auto-detected when PLUNK_API_KEY is the only configured provider credential. EMAIL_FROM must use an address or domain verified with Plunk.

Verify

pnpm setup:doctor reports Email (plunk): OK. Trigger an authentication email, such as a sign-up verification or password reset, and confirm Plunk delivers it.

For deeper coverage (delivery modes, React Email templates, swapping providers), see Email.

AWS logoStorageUse a public S3-compatible bucket for avatars and organization logos, with a separate private bucket for chat attachments.

Choose a provider and set the public asset env vars

The public bucket stores avatars and organization logos. Choose one provider configuration:

Native AWS needs a region and no custom endpoint. Keep path-style URLs disabled.

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-public-images-bucket"
NEXT_PUBLIC_S3_PUBLIC_URL="https://your-public-images-bucket.s3.us-east-1.amazonaws.com"

R2 uses an account-scoped endpoint and auto for the region.

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-public-images-bucket"
NEXT_PUBLIC_S3_PUBLIC_URL="https://pub-<bucket-id>.r2.dev"

MinIO and most self-hosted S3-compatible servers use path-style URLs. Set the endpoint to your MinIO host.

AWS_REGION="auto"
AWS_ENDPOINT_URL_S3="https://minio.example.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-public-images-bucket"
NEXT_PUBLIC_S3_PUBLIC_URL="https://minio.example.com/your-public-images-bucket"

Configure the buckets

Chat attachments do not use the public asset bucket. Create a separate private bucket with public access blocked, then set its server-only name:

S3_BUCKET_NAME_ATTACHMENTS="your-private-attachments-bucket"

Apply CORS to every bucket receiving browser uploads. Allowed origins are exact origin values, such as http://localhost:3000 for development and https://example.com for production. Also add a lifecycle rule that expires tmp/ objects after one day. See Storage required bucket configuration for the rules.

The public bucket configuration enables avatars and organization logos only. Chat attachments remain disabled until S3_BUCKET_NAME_ATTACHMENTS points to a separate private bucket.

Verify

pnpm setup:doctor reports Storage uploads: OK. Visit /dashboard/personal-settings, upload an avatar, and confirm it persists and appears in the navigation.

For deeper coverage (the presign + finalize pipeline, image validation, what gets stored where), see Storage.

Vercel logoAI (Vercel AI Gateway)Route supported chat models through Vercel AI Gateway with one app credential.

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

  1. Sign in at vercel.com.
  2. Open the AI Gateway page from your dashboard.
  3. Create a new API key. Copy the value.

Set the env var

VarExample
AI_GATEWAY_API_KEYvck_...

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.

PostHog logoAnalytics (PostHog)Enable product analytics, web analytics, and session replay with a PostHog project key.

Create a PostHog project

Sign up at PostHog, create a project, then copy the Project API Key from Project Settings.

Set the env vars

NEXT_PUBLIC_POSTHOG_KEY enables analytics. The host variables are optional endpoint overrides.

VarStatusValue
NEXT_PUBLIC_POSTHOG_KEYRequired to enable analyticsphc_...
NEXT_PUBLIC_POSTHOG_HOSTOptionalDefaults to https://us.i.posthog.com
NEXT_PUBLIC_POSTHOG_UI_HOSTOptionalDefaults to https://us.posthog.com

Leave the host overrides blank for PostHog US Cloud. For EU Cloud, set them to https://eu.i.posthog.com and https://eu.posthog.com; self-hosted projects use their own endpoints.

Verify

pnpm setup:doctor reports PostHog analytics: OK. Trigger an action in the app, then confirm it appears in Live Events in PostHog.

For typed events, identification, session replay, and reverse-proxy setup, see Analytics.

PostHog logoMonitoring (PostHog Source Maps and Logs)Builds 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. Set them in the environment that runs next build, not only in the runtime environment.

Create a personal API key

In PostHog, open Settings, choose Personal API Keys, then create a key with Project: write scope. Copy the key (phx_...) and the numeric Project ID from Project Settings.

Set the env vars

VarExample
POSTHOG_API_KEYphx_...
POSTHOG_PROJECT_ID12345

Verify

Run a fresh pnpm build and confirm it succeeds with both build-time variables present. Deploy that build, trigger a test error, and confirm the issue appears in PostHog with a de-minified stack trace.

For deeper coverage (the three pipelines, structured logging via OTLP, the reverse proxy), see Monitoring.

Upstash logoUpstash abuse protectionRequired for productionAdd shared rate limits for protected flows. Production requires both Upstash values and fails closed without them.

Create an Upstash Redis database

Sign up at Upstash, create a Redis database, then open its REST API panel. Copy the REST URL and REST Token.

Set the env vars

VarExample
UPSTASH_REDIS_REST_URLhttps://us1-active-xxx.upstash.io
UPSTASH_REDIS_REST_TOKENAX...

Verify

pnpm setup:doctor reports Abuse protection: OK. To exercise the same-IP contact.submit rule, send six requests from one IP within 10 minutes using different email addresses; the sixth is rate-limited. Reusing one email can hit its separate three-per-hour rule first.

For deeper coverage (per-surface limits, fail-open vs fail-closed policy, Redis tuning), see Security: Abuse Protection (Upstash).

Where To Go Next

Was this page helpful?

On this page