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