SaaS Starter
Platform admin

First-run bootstrap

How the very first super-admin account gets created.

The boilerplate ships with no built-in super-admin. On the very first run, the application detects an empty user table and redirects every authenticated and unauthenticated request to /setup. The wizard creates the first user and atomically promotes them to platformAdmin = true in the same serializable transaction.

Flow

  1. A user opens any URL on a fresh install.
  2. Next.js middleware fetches GET /api/v1/platform/bootstrap/status. If needsBootstrap === true, it 307-redirects to /setup.
  3. The wizard collects email, password, name; the form is rate-limited under authIpLimiter.
  4. POST /api/v1/platform/bootstrap creates the user via BetterAuth's signUp, then runs a serializable Postgres transaction that:
    • Re-checks count(platformAdmin = true AND deletedAt IS NULL) === 0.
    • Sets platformAdmin = true, emailVerified = true, onboardingCompletedAt = NOW().
    • Two concurrent bootstrap calls cannot both win — Postgres aborts one with a serialization failure and the use case surfaces it as ConflictError.
  5. The user is auto-signed-in (cookie set), bootstrap_completed is recorded in audit, and the next request lands on /admin.

The middleware caches needsBootstrap for 30 seconds; once it goes false, it stays false until the edge worker restarts. The only path back to first-run is wiping the DB.

Recovery CLI

If the database loses every platform admin (manual UPDATE, an incident wiped the user, etc.), the wizard won't trigger because it only fires on zero users. Use the recovery CLI:

bun run platform:promote -- [email protected]

This grants platformAdmin = true to an existing user via a one-shot script — no HTTP, no auth, just direct DB. Source: apps/server/scripts/platform-promote.ts.

[!WARNING] The recovery CLI bypasses the platform-admin gate entirely. In production, treat the script's invocation site (typically a SSH bastion or a Railway / Fly shell) as a privileged surface.

After bootstrap

The first owner has platformAdmin: true but no org memberships. They can:

  • Visit /admin immediately (the dashboard-shell onboarding gate has been bypassed for them).
  • Create their own org from /settings/organization if they need to operate as a tenant too.
  • Promote additional admins from /admin/users/[id] (see Managing admins).

On this page