Setup
After Quickstart, configure the integrations your app needs and verify each one with the setup doctor.
Last updated on
9 min readThe Setup Doctor
Run pnpm setup:doctor after editing apps/web/.env. It checks the three core variables and reports which optional integrations are ready.
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 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 field | Development app | Production app |
|---|---|---|
| Homepage URL | http://localhost:3000 | https://example.com |
| Authorization callback URL | http://localhost:3000/api/auth/callback/github | https://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.
| Var | Development | Production |
|---|---|---|
GITHUB_CLIENT_ID | Development app Client ID | Production app Client ID |
GITHUB_CLIENT_SECRET | generated-by-github | generated-by-github |
NEXT_PUBLIC_APP_URL | http://localhost:3000 | https://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 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 field | Development | Production |
|---|---|---|
| Authorized JavaScript origins | http://localhost:3000 | https://example.com |
| Authorized redirect URIs | http://localhost:3000/api/auth/callback/google | https://example.com/api/auth/callback/google |
Create the client, then copy its Client ID and Client Secret.
Set the env vars
| Var | Example |
|---|---|
GOOGLE_CLIENT_ID | 1234567890-abc.apps.googleusercontent.com |
GOOGLE_CLIENT_SECRET | GOCSPX-... |
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 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
| Var | Example |
|---|---|
NEXT_PUBLIC_TURNSTILE_SITE_KEY | 0x4AAAAAAA... |
TURNSTILE_SECRET_KEY | 0x4AAAAAAA... |
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 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/stripeThe 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
| 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... |
STRIPE_PRICE_ID_ULTRA_MONTHLY | price_1MxGhi... |
STRIPE_PRICE_ID_ULTRA_YEARLY | price_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)
| Var | Status | Example |
|---|---|---|
PLUNK_API_KEY | Required | sk_pk_... |
EMAIL_FROM | Required | SyntaxKit <noreply@example.com> |
EMAIL_DELIVERY_MODE | Optional explicit override | plunk |
CONTACT_FORM_TO_EMAIL | Optional; public contact form only | hello@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.
StorageUse 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.
AI (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
- 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)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.
| Var | Status | Value |
|---|---|---|
NEXT_PUBLIC_POSTHOG_KEY | Required to enable analytics | phc_... |
NEXT_PUBLIC_POSTHOG_HOST | Optional | Defaults to https://us.i.posthog.com |
NEXT_PUBLIC_POSTHOG_UI_HOST | Optional | Defaults 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.
Monitoring (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
| Var | Example |
|---|---|
POSTHOG_API_KEY | phx_... |
POSTHOG_PROJECT_ID | 12345 |
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 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
| 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. 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
Quickstart
Clone, configure three environment variables, and have SyntaxKit running locally in about five minutes. Optional integrations live in Setup.
Troubleshooting
Symptom-indexed fixes grouped by subsystem. Expand an entry for the cause, a specific fix, and a link to deeper docs. Start with pnpm setup:doctor, or use Cmd-F to match what you're seeing.
