Environment Variables
Complete guide to configuring Unstack Pro environment variables
Environment Variables
Unstack Pro reads configuration from two separate places:
- The Next.js environment —
.env.localin local development, or your host's environment variables (e.g. Vercel) in production. - The Convex deployment environment — set with
bunx convex env set NAME "value"or from the Convex dashboard → Settings → Environment Variables.
Some variables live in only one of these; a few must be set in both. Each
variable below is labelled with the environment(s) it belongs to. The
.env.example file in the repo is
split into the same two sections.
Security Notice: Never commit your .env.local file to version control. Keep your secrets secure!
Quick Setup
Copy the example file and fill in your values:
cp .env.example .env.localThen set the Convex-side variables with the CLI (with bun convex dev running):
bunx convex env set NAME "value"Section 1 — Next.js environment (.env.local)
These are read by the Next.js app. NEXT_PUBLIC_* values are also inlined into
the browser bundle. In production, set the same values in your host's environment
variables (see Deployment).
NEXT_PUBLIC_CONVEX_URL
Required: Yes · Environment: Next.js
Your Convex cloud deployment URL (….convex.cloud), printed by bun convex dev.
NEXT_PUBLIC_CONVEX_URL="https://your-deployment.convex.cloud"NEXT_PUBLIC_CONVEX_SITE_URL
Required: Yes · Environment: Next.js
The Convex HTTP Actions URL — the same deployment on the .convex.site
domain. Take NEXT_PUBLIC_CONVEX_URL and swap .convex.cloud for .convex.site.
Required by the auth proxy in lib/auth-server.ts.
NEXT_PUBLIC_CONVEX_SITE_URL="https://your-deployment.convex.site"NEXT_PUBLIC_APP_URL
Required: Yes · Environment: Next.js and Convex
The public URL of this Next.js app. Used for SEO metadata, the sitemap, email
links, and the passkey origin. Use http://localhost:3000 for local dev.
NEXT_PUBLIC_APP_URL="http://localhost:3000"This value is also needed on the Convex deployment (Convex-side email links and the passkey origin use it). Set it in both places.
NEXT_PUBLIC_APP_NAME
Required: Yes · Environment: Next.js and Convex
Display name shown throughout the UI and transactional emails. Email templates run on Convex, so set this in both environments.
NEXT_PUBLIC_APP_NAME="Unstack Pro"NEXT_PUBLIC_SUPPORT_EMAIL
Required: No · Environment: Next.js
Support address surfaced in the landing page / FAQ copy. Falls back to a placeholder if unset.
NEXT_PUBLIC_SUPPORT_EMAIL="support@yourcompany.com"AUTUMN_SECRET_KEY
Required: Yes (for billing) · Environment: Next.js and Convex
Your Autumn billing secret key (starts with am_sk_).
AUTUMN_SECRET_KEY must be set in both environments. The Next.js billing
routes (app/api/autumn/[...all]/route.ts) read it at runtime in the Node
runtime, and the Convex auth hooks read it on the deployment. Setting it in
only one place will break checkout/portal or seat syncing.
AUTUMN_SECRET_KEY="am_sk_..."Sentry (optional)
Required: No · Environment: Next.js
Error monitoring. Leave all of these unset to disable Sentry entirely — the SDK
no-ops when NEXT_PUBLIC_SENTRY_DSN is missing, and builds skip source-map upload
when SENTRY_AUTH_TOKEN is missing.
NEXT_PUBLIC_SENTRY_DSN=""
SENTRY_ORG=""
SENTRY_PROJECT=""
SENTRY_AUTH_TOKEN=""| Variable | Purpose |
|---|---|
NEXT_PUBLIC_SENTRY_DSN | Sentry project DSN. When empty, Sentry is disabled. |
SENTRY_ORG | Sentry org slug (build-time, for source maps). |
SENTRY_PROJECT | Sentry project slug (build-time, for source maps). |
SENTRY_AUTH_TOKEN | Token used to upload source maps at build time. |
CONVEX_DEPLOYMENT is written into .env.local automatically by bun convex dev
— you don't set it by hand. It identifies which deployment the CLI talks to.
Section 2 — Convex deployment environment
These live on the Convex deployment, not in .env.local. Set each with
bunx convex env set NAME "value" (or via the Convex dashboard → Settings →
Environment Variables).
BETTER_AUTH_SECRET
Required: Yes · Environment: Convex
Random secret used by Better Auth for session/token encryption. The JWKS private key is encrypted with it, so set this before generating JWKS.
Generate with:
openssl rand -base64 32JWKS
Required: Yes · Environment: Convex
The RS256 signing key set for Convex JWTs. convex/auth.config.ts reads the
public key set from this variable; Better Auth signs tokens with the stored
private key.
The stack ships a Convex action that generates the key pair if none exists and
returns it in the exact shape the auth config expects. With bun convex dev
running (so the functions are deployed):
# Prints the JWKS document set.
bunx convex run betterAuth/auth:getLatestJwksSet the output as the JWKS env var. On macOS/Linux you can do it in one step:
bunx convex env set JWKS "$(bunx convex run betterAuth/auth:getLatestJwks)"On Windows, copy the printed JSON and paste it into
bunx convex env set JWKS '…'.
To rotate keys, run bunx convex run betterAuth/auth:rotateKeys and set the
returned value as JWKS again. Key rotation logs out all users — do it only
when necessary, during low-traffic periods.
BETTER_AUTH_URL
Required: No (kept for the CLI) · Environment: Convex
Unused at runtime — kept only for the Better Auth CLI. The runtime uses
NEXT_PUBLIC_APP_URL instead, so you don't normally need to touch this.
BETTER_AUTH_URL="http://localhost:3000"EMAIL_FROM
Required: Yes · Environment: Convex
A sender address verified in Resend. Every email flow throws if this is missing.
EMAIL_FROM="Unstack Pro <noreply@yourdomain.com>"Format: "Display Name <email@domain.com>".
RESEND_API_KEY
Required: Yes · Environment: Convex
Your Resend API key for transactional email (verification, OTP, invitations).
RESEND_API_KEY="re_..."AUTUMN_SECRET_KEY
Required: Yes (for billing) · Environment: Convex and Next.js
Same key as in Section 1 — the Convex auth hooks read it for seat syncing and the Pro-plan gate. See the callout under Section 1: it must be set in both environments.
PASSKEY_RP_ID
Required: Yes · Environment: Convex
The Relying Party ID for WebAuthn/passkeys — the bare public domain, no scheme
or port. Use localhost for local dev; in production it must exactly match your
deployed domain.
PASSKEY_RP_ID="localhost"For production with subdomains, use the root domain (e.g. example.com, not
app.example.com) so passkeys work across all subdomains. NEXT_PUBLIC_APP_URL
must be the matching https:// origin.
NEXT_PUBLIC_APP_URL / NEXT_PUBLIC_APP_NAME
Also set these on the Convex deployment (same values as .env.local) — Convex-side
email links and the passkey origin use NEXT_PUBLIC_APP_URL, and email templates
use NEXT_PUBLIC_APP_NAME.
Setting Convex environment variables
# Auth
bunx convex env set BETTER_AUTH_SECRET "$(openssl rand -base64 32)"
bunx convex env set JWKS "$(bunx convex run betterAuth/auth:getLatestJwks)"
# Email
bunx convex env set RESEND_API_KEY "re_..."
bunx convex env set EMAIL_FROM "Unstack Pro <noreply@yourdomain.com>"
# Billing (also required in .env.local)
bunx convex env set AUTUMN_SECRET_KEY "am_sk_..."
# Application (same values as .env.local)
bunx convex env set NEXT_PUBLIC_APP_URL "http://localhost:3000"
bunx convex env set NEXT_PUBLIC_APP_NAME "Unstack Pro"
# Passkeys
bunx convex env set PASSKEY_RP_ID "localhost"You can view and manage Convex environment variables in your Convex dashboard.
Complete .env.local example
# --- Section 1: Next.js environment ---
NEXT_PUBLIC_CONVEX_URL="https://your-deployment.convex.cloud"
NEXT_PUBLIC_CONVEX_SITE_URL="https://your-deployment.convex.site"
NEXT_PUBLIC_APP_URL="http://localhost:3000"
NEXT_PUBLIC_APP_NAME="Unstack Pro"
NEXT_PUBLIC_SUPPORT_EMAIL="support@yourcompany.com"
# Required in BOTH environments (also set on Convex)
AUTUMN_SECRET_KEY="am_sk_..."
# Optional — Sentry (leave empty to disable)
NEXT_PUBLIC_SENTRY_DSN=""
SENTRY_ORG=""
SENTRY_PROJECT=""
SENTRY_AUTH_TOKEN=""The Convex-side variables (BETTER_AUTH_SECRET, JWKS, EMAIL_FROM,
RESEND_API_KEY, AUTUMN_SECRET_KEY, PASSKEY_RP_ID, NEXT_PUBLIC_APP_URL,
NEXT_PUBLIC_APP_NAME) are set with bunx convex env set, not in .env.local.
Environment Variable Checklist
Before deploying, make sure these are set:
Next.js environment (.env.local / host):
-
NEXT_PUBLIC_CONVEX_URL -
NEXT_PUBLIC_CONVEX_SITE_URL -
NEXT_PUBLIC_APP_URL -
NEXT_PUBLIC_APP_NAME -
AUTUMN_SECRET_KEY -
NEXT_PUBLIC_SUPPORT_EMAIL(optional) - Sentry vars (optional)
Convex deployment:
-
BETTER_AUTH_SECRET -
JWKS -
EMAIL_FROM(verified Resend sender) -
RESEND_API_KEY -
AUTUMN_SECRET_KEY -
PASSKEY_RP_ID -
NEXT_PUBLIC_APP_URLandNEXT_PUBLIC_APP_NAME
Troubleshooting
"X is not defined" errors
When Convex code throws that a variable is missing, consult createAuthOptions
in convex/betterAuth/auth.ts for the full required set, and confirm the variable
is set on the Convex deployment (not just .env.local).
Billing routes 500 or seats don't sync
AUTUMN_SECRET_KEY is missing from one of the two environments. Set it in both
.env.local (Next.js runtime) and on the Convex deployment.
Passkeys not working
PASSKEY_RP_ID must match your domain exactly (localhost for dev, the root
domain in production), and NEXT_PUBLIC_APP_URL must be the matching origin.
Security Best Practices
- Never commit secrets: keep
.env.localin.gitignore. - Rotate JWKS periodically:
bunx convex run betterAuth/auth:rotateKeys, then set the output asJWKS(this logs everyone out). - Use different secrets for development and production.
- Restrict API keys to the minimum permissions necessary.
- Monitor access: review Convex and Resend logs regularly.
Production Reminder: Always use HTTPS in production. Never use http:// URLs
for NEXT_PUBLIC_APP_URL.