Better Auth Scaffold (Next.js + Drizzle)
Parameterized templates for bootstrapping a Better Auth setup in a Next.js App Router project using the Drizzle adapter. Each template enforces the conventions documented in references/conventions.md — file layout, plugin ordering, env handling, runtime selection.
When to Apply
Reference these templates when:
- Starting a new Next.js project that needs authentication
- Adding Better Auth to an existing Next.js + Drizzle codebase
- Refactoring a partial Better Auth setup that's missing the catch-all route, middleware, or
nextCookies() ordering
- Generating a per-feature variant (minimal, social, advanced) on top of an existing project
Setup
Required parameters
| Parameter |
Required |
Default |
Description |
preset |
no |
minimal |
minimal (email+password) | social (adds Google + GitHub) | advanced (social + twoFactor + magicLink) |
db_provider |
yes |
— |
pg | mysql | sqlite (Drizzle provider) |
app_name |
yes |
— |
Display name (becomes 2FA issuer in authenticator apps when preset=advanced) |
Optional parameters
| Parameter |
Default |
Description |
auth_path |
lib/auth.ts |
Server auth module path |
client_path |
lib/auth-client.ts |
Client module path |
api_route_path |
app/api/auth/[...all]/route.ts |
Catch-all route handler path |
protected_paths |
["/dashboard"] |
Routes the middleware guards |
If config.json already exists with values, the skill uses those; otherwise it asks the user.
Available Templates
| Template |
Output File |
When to emit |
auth.ts.template |
lib/auth.ts |
Always |
auth-client.ts.template |
lib/auth-client.ts |
Always |
route.ts.template |
app/api/auth/[...all]/route.ts |
Always |
middleware.ts.template |
middleware.ts |
When protected_paths is non-empty |
env.template |
.env.example |
Always |
db-schema-better-auth.ts.template |
db/schema/auth.ts |
Always (stub — replace with output of better-auth generate) |
db-index.ts.template |
db/index.ts |
When the project has no Drizzle client yet |
email.ts.template |
lib/email.ts |
When preset=advanced (and the file doesn't already exist) |
sign-in-page.tsx.template |
app/sign-in/page.tsx |
When the project has no sign-in page (closes the middleware redirect loop) |
permissions.ts.template |
lib/permissions.ts |
Optional starter for org/admin plugin work |
How to Use
Resolve parameters. Read config.json first; for any missing required parameter (db_provider, app_name), ask the user via AskUserQuestion.
Render each template. For each template file:
- Read the template.
- Substitute
{{placeholder}} values ({{app_name}}, {{db_provider}}, etc.) with the resolved parameter values.
- Apply
PRESET[...] conditional blocks using these rules:
- Find every pair of marker lines matching
// PRESET[<tags>] and // /PRESET[<tags>] (or /* PRESET[...] */ / # PRESET[...] for non-JS files — same syntax, different comment style).
- Parse
<tags> as a comma-separated list (e.g. social,advanced).
- If the chosen preset value is in
<tags> → remove ONLY the two marker lines; keep the lines between them.
- If the chosen preset value is NOT in
<tags> → remove the ENTIRE block including both marker lines.
- Markers may be nested (e.g.
PRESET[advanced] inside PRESET[minimal,social,advanced]); process from inside out.
- For Mustache-style iteration in
middleware.ts.template (// {{#protected_paths}} ... // {{/protected_paths}}), repeat the lines between the markers once per item in the list, substituting {{path}} with each value.
Write output files. Before writing, check if the target file already exists:
- If it doesn't exist → write it.
- If it exists and is identical → no-op.
- If it exists and differs → show a diff and ask the user (overwrite / merge / skip).
Run the CLI sequence. After all files are written:
npx @better-auth/cli@latest generate
npx drizzle-kit generate
npx drizzle-kit migrate
These commands replace the placeholder db/schema/auth.ts with the real schema and apply migrations.
Print next steps. Tell the user to:
- Fill in
.env.local (copy from .env.example)
- Generate a secret:
openssl rand -base64 32
- Register OAuth redirect URIs with each provider console (for
social/advanced presets)
- Implement
lib/email.ts to wire transactional email (for advanced preset)
Conventions
Read references/conventions.md for the rationale behind every convention these templates encode. Highlights:
lib/auth.ts is server-only (import "server-only")
nextCookies() is ALWAYS the last plugin in the array
- Middleware does a cookie-presence check only; real validation in pages
- All secrets via
process.env.*, never inline
- Sliding-window sessions with
cookieCache enabled
Related Skills
better-auth — Library/API Reference with 42 rules covering setup, sessions, security, plugins, and migration. The templates here are one canonical realization of those rules; read the rule for the underlying reasoning when you need to deviate.
Gotchas
See gotchas.md — initialized empty, populated as we discover them.
1---2name: better-auth-scaffold3description: Scaffolds a Better Auth setup in a Next.js (App Router) + Drizzle project — lib/auth.ts, lib/auth-client.ts, the /api/auth/[...all] route handler, middleware.ts, .env.example, and a permissions module. Produces convention-enforced templates for three plugin presets (minimal, social, advanced with twoFactor+magicLink). Trigger even when the user doesn't explicitly say "scaffold" — phrases like "set up Better Auth", "wire up auth", "initialize auth in this project", or "add auth to Next.js" should pull this in. Pairs with the `better-auth` skill, which covers the rules these templates encode.4---5# Better Auth Scaffold (Next.js + Drizzle)
6
7Parameterized templates for bootstrapping a Better Auth setup in a Next.js App Router project using the Drizzle adapter. Each template enforces the conventions documented in [`references/conventions.md`](references/conventions.md) — file layout, plugin ordering, env handling, runtime selection.
8
9## When to Apply
10
11Reference these templates when:
12- Starting a new Next.js project that needs authentication
13- Adding Better Auth to an existing Next.js + Drizzle codebase
14- Refactoring a partial Better Auth setup that's missing the catch-all route, middleware, or `nextCookies()` ordering
15- Generating a per-feature variant (minimal, social, advanced) on top of an existing project
16
17## Setup
18
19### Required parameters
20
21| Parameter | Required | Default | Description |
22|---|---|---|---|
23| `preset` | no | `minimal` | `minimal` (email+password) \| `social` (adds Google + GitHub) \| `advanced` (social + twoFactor + magicLink) |
24| `db_provider` | yes | — | `pg` \| `mysql` \| `sqlite` (Drizzle provider) |
25| `app_name` | yes | — | Display name (becomes 2FA issuer in authenticator apps when preset=advanced) |
26
27### Optional parameters
28
29| Parameter | Default | Description |
30|---|---|---|
31| `auth_path` | `lib/auth.ts` | Server auth module path |
32| `client_path` | `lib/auth-client.ts` | Client module path |
33| `api_route_path` | `app/api/auth/[...all]/route.ts` | Catch-all route handler path |
34| `protected_paths` | `["/dashboard"]` | Routes the middleware guards |
35
36If `config.json` already exists with values, the skill uses those; otherwise it asks the user.
37
38## Available Templates
39
40| Template | Output File | When to emit |
41|---|---|---|
42| [`auth.ts.template`](assets/templates/auth.ts.template) | `lib/auth.ts` | Always |
43| [`auth-client.ts.template`](assets/templates/auth-client.ts.template) | `lib/auth-client.ts` | Always |
44| [`route.ts.template`](assets/templates/route.ts.template) | `app/api/auth/[...all]/route.ts` | Always |
45| [`middleware.ts.template`](assets/templates/middleware.ts.template) | `middleware.ts` | When `protected_paths` is non-empty |
46| [`env.template`](assets/templates/env.template) | `.env.example` | Always |
47| [`db-schema-better-auth.ts.template`](assets/templates/db-schema-better-auth.ts.template) | `db/schema/auth.ts` | Always (stub — replace with output of `better-auth generate`) |
48| [`db-index.ts.template`](assets/templates/db-index.ts.template) | `db/index.ts` | When the project has no Drizzle client yet |
49| [`email.ts.template`](assets/templates/email.ts.template) | `lib/email.ts` | When `preset=advanced` (and the file doesn't already exist) |
50| [`sign-in-page.tsx.template`](assets/templates/sign-in-page.tsx.template) | `app/sign-in/page.tsx` | When the project has no sign-in page (closes the middleware redirect loop) |
51| [`permissions.ts.template`](assets/templates/permissions.ts.template) | `lib/permissions.ts` | Optional starter for org/admin plugin work |
52
53## How to Use
54
551. **Resolve parameters.** Read `config.json` first; for any missing required parameter (`db_provider`, `app_name`), ask the user via `AskUserQuestion`.
56
572. **Render each template.** For each template file:
58 - Read the template.
59 - Substitute `{{placeholder}}` values (`{{app_name}}`, `{{db_provider}}`, etc.) with the resolved parameter values.
60 - Apply `PRESET[...]` conditional blocks using these rules:
61 1. Find every pair of marker lines matching `// PRESET[<tags>]` and `// /PRESET[<tags>]` (or `/* PRESET[...] */` / `# PRESET[...]` for non-JS files — same syntax, different comment style).
62 2. Parse `<tags>` as a comma-separated list (e.g. `social,advanced`).
63 3. If the chosen preset value is in `<tags>` → remove ONLY the two marker lines; keep the lines between them.
64 4. If the chosen preset value is NOT in `<tags>` → remove the ENTIRE block including both marker lines.
65 5. Markers may be nested (e.g. `PRESET[advanced]` inside `PRESET[minimal,social,advanced]`); process from inside out.
66 - For Mustache-style iteration in `middleware.ts.template` (`// {{#protected_paths}}` ... `// {{/protected_paths}}`), repeat the lines between the markers once per item in the list, substituting `{{path}}` with each value.
67
683. **Write output files.** Before writing, check if the target file already exists:
69 - If it doesn't exist → write it.
70 - If it exists and is identical → no-op.
71 - If it exists and differs → show a diff and ask the user (overwrite / merge / skip).
72
734. **Run the CLI sequence.** After all files are written:
74 ```bash
75 npx @better-auth/cli@latest generate
76 npx drizzle-kit generate
77 npx drizzle-kit migrate
78 ```
79 These commands replace the placeholder `db/schema/auth.ts` with the real schema and apply migrations.
80
815. **Print next steps.** Tell the user to:
82 - Fill in `.env.local` (copy from `.env.example`)
83 - Generate a secret: `openssl rand -base64 32`
84 - Register OAuth redirect URIs with each provider console (for `social`/`advanced` presets)
85 - Implement `lib/email.ts` to wire transactional email (for `advanced` preset)
86
87## Conventions
88
89Read [`references/conventions.md`](references/conventions.md) for the rationale behind every convention these templates encode. Highlights:
90
91- `lib/auth.ts` is server-only (`import "server-only"`)
92- `nextCookies()` is ALWAYS the last plugin in the array
93- Middleware does a cookie-presence check only; real validation in pages
94- All secrets via `process.env.*`, never inline
95- Sliding-window sessions with `cookieCache` enabled
96
97## Related Skills
98
99- [`better-auth`](../better-auth/SKILL.md) — Library/API Reference with 42 rules covering setup, sessions, security, plugins, and migration. The templates here are one canonical realization of those rules; read the rule for the underlying reasoning when you need to deviate.
100
101## Gotchas
102
103See [`gotchas.md`](gotchas.md) — initialized empty, populated as we discover them.