Unstack Pro Docs

Getting Started

Set up Unstack Pro and get your development environment running

Get up and running fast. Auth is already configured — you just connect the services and set your environment variables.

The package manager is Bun. All commands use bun — do not substitute npm or pnpm.

Choose Your Edition

Unstack Pro ships in two editions. Pick one with the switcher below — your choice is remembered and follows you across every page in these docs.

Better Auth runs inside Next.js and talks to PostgreSQL through Drizzle ORM. There is a single environment surface — .env.local — one process to run, and schema changes are applied as SQL migrations.

Better Auth runs inside Convex. There are two environment surfaces — .env.local for Next.js, and the Convex deployment environment — and two processes to run during development.

Setup

Prerequisites

You'll need accounts on the following services (all have free tiers):

  • Bun — the package manager and runtime.
  • A PostgreSQL database — any provider works. The app connects with pg (node-postgres) rather than a vendor-specific driver, so Neon and Supabase are convenient recommendations, not requirements; a Postgres you run yourself is equally fine.
  • Resend — transactional email (verification, OTP, invitations). You'll need an API key and a verified sender.
  • Autumn — organization-scoped subscription billing.
  • (Optional) Sentry — error monitoring. The app runs fine without it.
  • Bun — the package manager and runtime.
  • Convex — real-time database + backend. Auth runs inside Convex.
  • Resend — transactional email (verification, OTP, invitations). You'll need an API key and a verified sender.
  • Autumn — organization-scoped subscription billing.
  • (Optional) Sentry — error monitoring. The app runs fine without it.

Get the code

The Postgres-edition template repository is not published yet; the Convex edition is the one distributed today.

git clone https://github.com/unstack-pro/nextjs-convex.git  # Purchasing grants access to the private repo
cd nextjs-convex

Install

bun install

Configure environment

Copy the example file and fill in your values. See Environment Variables for the full reference.

cp .env.example .env.local

.env.local is the only place configuration lives — there is no second deployment environment to populate.

.env.local
DATABASE_URL="postgresql://user:password@host/dbname?sslmode=require"

NEXT_PUBLIC_APP_URL="http://localhost:3000"
NEXT_PUBLIC_APP_NAME="Unstack Pro"

# openssl rand -base64 32
BETTER_AUTH_SECRET="replace-me"

EMAIL_FROM="Unstack Pro <noreply@yourdomain.com>"
RESEND_API_KEY="re_..."
AUTUMN_SECRET_KEY="am_sk_..."
PASSKEY_RP_ID="localhost"

.env.local holds the Next.js side only. The secrets Better Auth itself reads live on the Convex deployment and are set in the next step.

.env.local
# Printed by `bun convex dev` (….convex.cloud) — swap .cloud for .site for the site URL
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"

# Required in BOTH the Next.js env AND the Convex deployment
AUTUMN_SECRET_KEY="am_sk_..."

Set up the database

bun run db:migrate

That applies the SQL migrations in drizzle/ to DATABASE_URL, creating the Better Auth tables and the app tables.

drizzle-kit does not load .env.local, so run bare it sees DATABASE_URL as undefined. Export the file into the shell first:

set -a && . ./.env.local && set +a && bunx drizzle-kit migrate
bun convex dev          # in a dedicated terminal — keep it running

The first run logs you into Convex, creates a deployment, and pushes the schema/functions. It also writes CONVEX_DEPLOYMENT and the Convex URLs into .env.local. Leave it running throughout development.

With the deployment up, set the deployment-side variables. These do not live in .env.local — set each with bunx convex env set NAME "value" (or via the Convex dashboard):

# Set this before generating JWKS in the next step
bunx convex env set BETTER_AUTH_SECRET "$(openssl rand -base64 32)"

bunx convex env set RESEND_API_KEY "re_..."
bunx convex env set EMAIL_FROM "Unstack Pro <noreply@yourdomain.com>"

# Also required in .env.local
bunx convex env set AUTUMN_SECRET_KEY "am_sk_..."

# Same values as .env.local
bunx convex env set PASSKEY_RP_ID "localhost"
bunx convex env set NEXT_PUBLIC_APP_URL "http://localhost:3000"
bunx convex env set NEXT_PUBLIC_APP_NAME "Unstack Pro"

EMAIL_FROM must be a Resend-verified sender — every email flow throws if it's missing.

Generate signing keys

Nothing to generate. Better Auth runs inside Next.js and issues its own sessions, so there is no key pair and no JWKS variable.

Better Auth signs the Convex JWTs with an RS256 key pair. convex/auth.config.ts reads the public key set from the JWKS env var, and Better Auth signs with the stored private key — which is encrypted with BETTER_AUTH_SECRET, so that must already be set.

With bun convex dev running (so the functions are deployed):

# Prints the JWKS document set
bunx convex run betterAuth/auth:getLatestJwks

Set the output as the JWKS env var. On macOS/Linux, one step:

bunx convex env set JWKS "$(bunx convex run betterAuth/auth:getLatestJwks)"

On Windows, copy the printed JSON array and paste it into the same command in place of the substitution.

To rotate keys later, run bunx convex run betterAuth/auth:rotateKeys and set the result as JWKS again — this logs out all users.

Configure Autumn billing

Subscriptions are defined in autumn.config.ts (a free_plan and a seat-metered pro plan). Push them to your Autumn account and wire up the secret key:

bunx atmn login          # authenticate the Autumn CLI
bunx atmn push           # create the products from autumn.config.ts

Then set AUTUMN_SECRET_KEY in .env.local using the key from the Autumn dashboard.

Then set AUTUMN_SECRET_KEY in both .env.local and on the Convex deployment, using the key from the Autumn dashboard — the Next.js billing routes and the Convex auth hooks each read it.

Passkeys

PASSKEY_RP_ID must be the bare public domain that serves the app — no scheme, no port. For local development that is localhost. In production it must exactly match your deployed domain, and NEXT_PUBLIC_APP_URL must be the matching https:// origin.

Set it in .env.local:

PASSKEY_RP_ID="localhost"

Set it on the Convex deployment, where Better Auth reads it:

bunx convex env set PASSKEY_RP_ID "localhost"

Run the app

bun dev                  # Next.js dev server (Turbopack)

Open http://localhost:3000. 🎉

bun dev                  # Next.js dev server (Turbopack)

Open http://localhost:3000. Keep bun convex dev running in its own terminal — the app does not function without it. 🎉

Commands

bun dev                  # Next dev server (turbopack)
bun run build            # tsgo --noEmit && next build --turbopack
bun run db:generate      # drizzle-kit generate (SQL migration from the schema)
bun run db:migrate       # drizzle-kit migrate (apply to DATABASE_URL)
bun run auth:schema      # regenerate lib/db/auth-schema.ts from lib/auth.ts
bun run lint             # biome lint .
bun run format           # biome format --write .
bun dev                  # Next dev server (turbopack)
bun convex dev           # Convex backend — required for the app to function
bun run build            # tsgo --noEmit && next build --turbopack
bun run lint             # biome lint .
bun run format           # biome format --write .

Verify Installation

  1. Visit the homepage: navigate to http://localhost:3000
  2. Register an account: create a test account and complete email verification
  3. Confirm the data landed: check that your user row was created
  4. Explore features: log in, create an organization, invite a member

For step 3, query the database directly (select * from "user"; against DATABASE_URL) or open your provider's table browser.

For step 3, open the Convex dashboard and watch the user table fill in.

Next Steps

Common Issues

The App Can't Reach Its Data

Check that DATABASE_URL is reachable from where you're running the app (most hosted providers require ?sslmode=require), and that the migrations have actually been applied:

set -a && . ./.env.local && set +a && bunx drizzle-kit migrate

Make sure your Convex dev server is running:

bun convex dev

Environment Variable Not Found

Double-check that:

  • .env.local exists in the root directory
  • The dev server was restarted after you edited it
  • drizzle-kit commands were given DATABASE_URL explicitly — they do not read .env.local

Double-check that:

  • .env.local exists in the root directory
  • Convex-side variables are set via bunx convex env set (or the dashboard); putting them in .env.local does not reach the deployment
  • If Convex throws that a variable is missing, consult createAuthOptions in convex/betterAuth/auth.ts

Authentication Errors

Ensure:

  • BETTER_AUTH_SECRET is set in .env.local
  • NEXT_PUBLIC_APP_URL matches your dev URL
  • Every migration in drizzle/ has been applied

Ensure:

  • BETTER_AUTH_SECRET and JWKS are set on the Convex deployment
  • NEXT_PUBLIC_APP_URL matches your dev URL
  • bun convex dev is running

Never commit your .env.local file to version control. It contains sensitive secrets.

On this page