@withmata/blueprints 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/.claude/commands/audit.md +179 -0
  2. package/.claude/commands/discover.md +92 -0
  3. package/.claude/commands/new-blueprint.md +265 -0
  4. package/.claude/commands/new-project.md +230 -0
  5. package/.claude/commands/scaffold-auth.md +310 -0
  6. package/.claude/commands/scaffold-db.md +270 -0
  7. package/.claude/commands/scaffold-foundation.md +158 -0
  8. package/.cursor/commands/audit.md +179 -0
  9. package/.cursor/commands/discover.md +92 -0
  10. package/.cursor/commands/new-blueprint.md +265 -0
  11. package/.cursor/commands/new-project.md +230 -0
  12. package/.cursor/commands/scaffold-auth.md +310 -0
  13. package/.cursor/commands/scaffold-db.md +270 -0
  14. package/.cursor/commands/scaffold-foundation.md +158 -0
  15. package/.opencode/commands/audit.md +183 -0
  16. package/.opencode/commands/discover.md +96 -0
  17. package/.opencode/commands/new-blueprint.md +269 -0
  18. package/.opencode/commands/new-project.md +234 -0
  19. package/.opencode/commands/scaffold-auth.md +314 -0
  20. package/.opencode/commands/scaffold-db.md +274 -0
  21. package/.opencode/commands/scaffold-foundation.md +162 -0
  22. package/ENGINEERING.md +47 -0
  23. package/blueprints/discovery/product-discovery/BLUEPRINT.md +361 -0
  24. package/blueprints/discovery/product-discovery/templates/archetype-template.md +143 -0
  25. package/blueprints/discovery/product-discovery/templates/product-brief.md +65 -0
  26. package/blueprints/discovery/product-discovery/templates/product-thesis.md +64 -0
  27. package/blueprints/discovery/product-discovery/templates/research-summary.md +43 -0
  28. package/blueprints/features/auth-better-auth/BLUEPRINT.md +794 -0
  29. package/blueprints/features/auth-better-auth/files/client/auth-client.ts +31 -0
  30. package/blueprints/features/auth-better-auth/files/client/context/organization.ts +236 -0
  31. package/blueprints/features/auth-better-auth/files/client/hooks/use-create-organization.ts +45 -0
  32. package/blueprints/features/auth-better-auth/files/client/hooks/use-has-permission.ts +26 -0
  33. package/blueprints/features/auth-better-auth/files/client/hooks/use-organization.ts +64 -0
  34. package/blueprints/features/auth-better-auth/files/client/schema/auth.ts +21 -0
  35. package/blueprints/features/auth-better-auth/files/client/schema/organization.ts +51 -0
  36. package/blueprints/features/auth-better-auth/files/client/types/organization.ts +20 -0
  37. package/blueprints/features/auth-better-auth/files/db/auth-schema.ts +184 -0
  38. package/blueprints/features/auth-better-auth/files/db/drizzle.config.ts +21 -0
  39. package/blueprints/features/auth-better-auth/files/middleware/route-protection.ts +84 -0
  40. package/blueprints/features/auth-better-auth/files/server/auth-db.ts +18 -0
  41. package/blueprints/features/auth-better-auth/files/server/auth.ts +159 -0
  42. package/blueprints/features/auth-better-auth/files/server/env.ts +44 -0
  43. package/blueprints/features/auth-better-auth/files/server/middleware.ts +144 -0
  44. package/blueprints/features/db-drizzle-postgres/BLUEPRINT.md +596 -0
  45. package/blueprints/features/db-drizzle-postgres/files/db/drizzle.config.ts +18 -0
  46. package/blueprints/features/db-drizzle-postgres/files/db/env.example +3 -0
  47. package/blueprints/features/db-drizzle-postgres/files/db/package.json +33 -0
  48. package/blueprints/features/db-drizzle-postgres/files/db/src/client.ts +34 -0
  49. package/blueprints/features/db-drizzle-postgres/files/db/src/example-entity.ts +73 -0
  50. package/blueprints/features/db-drizzle-postgres/files/db/src/index.ts +8 -0
  51. package/blueprints/features/db-drizzle-postgres/files/db/src/scripts/seed.ts +50 -0
  52. package/blueprints/features/db-drizzle-postgres/files/db/tsconfig.json +13 -0
  53. package/blueprints/features/tailwind-v4/BLUEPRINT.md +29 -0
  54. package/blueprints/features/ui-shared-components/BLUEPRINT.md +35 -0
  55. package/blueprints/foundation/monorepo-turbo/BLUEPRINT.md +378 -0
  56. package/blueprints/foundation/monorepo-turbo/files/apps/web/app/globals.css +2 -0
  57. package/blueprints/foundation/monorepo-turbo/files/apps/web/app/layout.tsx +23 -0
  58. package/blueprints/foundation/monorepo-turbo/files/apps/web/app/page.tsx +7 -0
  59. package/blueprints/foundation/monorepo-turbo/files/apps/web/env.ts +13 -0
  60. package/blueprints/foundation/monorepo-turbo/files/apps/web/next.config.ts +12 -0
  61. package/blueprints/foundation/monorepo-turbo/files/apps/web/package.json +28 -0
  62. package/blueprints/foundation/monorepo-turbo/files/apps/web/postcss.config.mjs +5 -0
  63. package/blueprints/foundation/monorepo-turbo/files/apps/web/tsconfig.json +18 -0
  64. package/blueprints/foundation/monorepo-turbo/files/config/tailwind-config/package.json +14 -0
  65. package/blueprints/foundation/monorepo-turbo/files/config/tailwind-config/postcss.config.mjs +5 -0
  66. package/blueprints/foundation/monorepo-turbo/files/config/tailwind-config/shared-styles.css +88 -0
  67. package/blueprints/foundation/monorepo-turbo/files/config/typescript-config/base.json +19 -0
  68. package/blueprints/foundation/monorepo-turbo/files/config/typescript-config/nextjs.json +12 -0
  69. package/blueprints/foundation/monorepo-turbo/files/config/typescript-config/package.json +5 -0
  70. package/blueprints/foundation/monorepo-turbo/files/config/typescript-config/react-library.json +7 -0
  71. package/blueprints/foundation/monorepo-turbo/files/root/biome.json +46 -0
  72. package/blueprints/foundation/monorepo-turbo/files/root/gitignore +35 -0
  73. package/blueprints/foundation/monorepo-turbo/files/root/package.json +25 -0
  74. package/blueprints/foundation/monorepo-turbo/files/root/pnpm-workspace.yaml +5 -0
  75. package/blueprints/foundation/monorepo-turbo/files/root/turbo.json +30 -0
  76. package/dist/index.js +453 -0
  77. package/package.json +53 -0
@@ -0,0 +1,794 @@
1
+ # Auth Blueprint — Better Auth + Drizzle
2
+
3
+ ## Problem
4
+
5
+ Full authentication and authorization for a TypeScript monorepo: multi-provider social login, email/password with verification, password reset, organization-based multi-tenancy with role-based permissions (owner/admin/member), invitation system, and session management with cookie-based route protection.
6
+
7
+ ## Status
8
+
9
+ Complete.
10
+
11
+ ## Blueprint Dependencies
12
+
13
+ | Blueprint | Type | Why |
14
+ |-----------|------|-----|
15
+ | `features/db-drizzle-postgres` | required | Auth creates schema files inside the database layer following the schema-group pattern (per-group drizzle.config.ts, barrel exports, explicit schema lists). Without it, auth must create a minimal db structure that lacks the full patterns. |
16
+ | `foundation/monorepo-turbo` | recommended | Provides monorepo workspace structure (packages/, apis/, turbo tasks, pnpm workspaces). Works without it — adapts to single-repo projects automatically. |
17
+
18
+ ### If `db-drizzle-postgres` is not installed
19
+
20
+ The scaffold command should ask: "Auth requires a database package. Do you want to install the db-drizzle-postgres blueprint first? (Y/n)"
21
+
22
+ If the user accepts, run `/scaffold-db` first, then continue with auth. If the user declines, auth creates a minimal database structure with only the auth schema group — but warns that it will lack seed scripts, example entities, and the full schema-group conventions.
23
+
24
+ If the database directory already exists (from manual setup or another tool), auth proceeds normally — it detects and merges into the existing structure.
25
+
26
+ ---
27
+
28
+ ## Architecture
29
+
30
+ ### Core Pattern
31
+
32
+ Better Auth handles all auth logic (sign-in, sign-up, OAuth callbacks, session management, password reset, email verification) through a single handler mounted at `/api/auth/*`. The server validates sessions via `auth.api.getSession({ headers })`. The client uses `createAuthClient()` from `better-auth/react` for all auth operations.
33
+
34
+ ### Key Decisions
35
+
36
+ - **Separate auth database** (recommended) — Auth tables live in their own PostgreSQL database, isolated from application data. Independent migrations, clean separation. Can use a shared database if preferred.
37
+
38
+ - **Organization plugin** (optional) — Multi-tenant workspaces with invitations, roles (owner > admin > member), and per-org context. Omit for simpler single-tenant apps. See "Removing the Organization Plugin" below.
39
+
40
+ - **Server-side session with cookie cache** — Sessions stored in DB, validated server-side. A 5-minute cookie cache avoids a DB round-trip on every request. 30-day session expiry, 24-hour refresh interval.
41
+
42
+ - **Client context split** — Organization state is split into separate data and actions React contexts. Components consuming only actions don't re-render when data changes.
43
+
44
+ - **SWR mutation hooks** (recommended) — All auth mutations use `useSWRMutation`. Each flow has its own hook with typed error handling using discriminated error codes.
45
+
46
+ - **Email via Resend** (recommended) — Verification emails, password reset, and org invitations use Resend. Swappable to any provider.
47
+
48
+ ---
49
+
50
+ ## Hard Requirements vs Recommended Defaults
51
+
52
+ **Hard requirements:**
53
+ - Better Auth as the auth library
54
+ - Drizzle ORM as the database adapter
55
+
56
+ **Recommended defaults** — ask the user during scaffolding:
57
+
58
+ | Choice | Recommended | Alternatives |
59
+ |---|---|---|
60
+ | Database | PostgreSQL | MySQL, SQLite |
61
+ | Auth DB isolation | Separate database | Shared with app DB |
62
+ | Env validation | t3-env + Zod | raw process.env, other validators |
63
+ | Form validation | Zod | Valibot, Yup, ArkType |
64
+ | Data fetching hooks | SWR (`useSWRMutation`) | TanStack Query, raw fetch |
65
+ | Email provider | Resend | SendGrid, Postmark, AWS SES |
66
+ | Server framework | Hono (standalone) | Next.js API routes, Express, Fastify |
67
+
68
+ > **Note:** t3-env and Zod are project-wide preferences, not auth-specific. They may be extracted into their own blueprints in the future.
69
+
70
+ ---
71
+
72
+ ## Dependencies
73
+
74
+ ### npm packages
75
+
76
+ **Server (both modes):**
77
+ | Package | Version | Purpose |
78
+ |---|---|---|
79
+ | `better-auth` | ^1.4.6 | Auth library |
80
+ | `drizzle-orm` | ^0.45.1 | Database ORM |
81
+ | `pg` | ^8.18.0 | PostgreSQL driver |
82
+ | `@t3-oss/env-core` | ^0.13.8 | Env validation (Hono mode) |
83
+ | `zod` | ^4.x | Schema validation |
84
+ | `resend` | ^6.x | Email sending (optional) |
85
+ | `dotenv` | ^16.x | Env file loading |
86
+
87
+ **Server (Hono mode only):**
88
+ | Package | Version | Purpose |
89
+ |---|---|---|
90
+ | `hono` | ^4.9.x | HTTP framework |
91
+ | `@hono/node-server` | ^1.19.x | Node.js adapter |
92
+
93
+ **Client (Next.js frontend):**
94
+ | Package | Version | Purpose |
95
+ |---|---|---|
96
+ | `better-auth` | ^1.4.6 | Auth client |
97
+ | `swr` | ^2.x | Data fetching + mutations |
98
+ | `react-hook-form` | ^7.x | Form management |
99
+ | `@hookform/resolvers` | ^5.x | Zod resolver for forms |
100
+ | `zod` | ^4.x | Form validation |
101
+ | `@t3-oss/env-nextjs` | ^0.13.x | Env validation (Next.js) |
102
+
103
+ **DB package (dev):**
104
+ | Package | Version | Purpose |
105
+ |---|---|---|
106
+ | `drizzle-kit` | ^0.31.x | Migration generation + execution |
107
+
108
+ ### Environment Variables
109
+
110
+ | Variable | Where | Description |
111
+ |---|---|---|
112
+ | `AUTH_DATABASE_URL` | Server + DB | PostgreSQL connection URL for auth database |
113
+ | `BETTER_AUTH_URL` | Server | Base URL where auth API is accessible (for OAuth callbacks) |
114
+ | `BETTER_AUTH_SECRET` | Server | Secret for signing sessions/tokens. Generate: `openssl rand -base64 32` |
115
+ | `GOOGLE_CLIENT_ID` | Server | Google OAuth client ID |
116
+ | `GOOGLE_CLIENT_SECRET` | Server | Google OAuth client secret. Redirect URI: `{BETTER_AUTH_URL}/api/auth/callback/google` |
117
+ | `RESEND_API_KEY` | Server | Resend API key for transactional emails |
118
+ | `FRONTEND_DOMAIN` | Server | Frontend URL for CORS, trusted origins, email links |
119
+ | `PORT` | Server (Hono) | Port for standalone server |
120
+ | `NEXT_PUBLIC_API_URL` | Client | Backend API URL |
121
+ | `NEXT_PUBLIC_FRONTEND_URL` | Client | Frontend's own URL |
122
+
123
+ ---
124
+
125
+ ## Monorepo Wiring
126
+
127
+ This section documents how auth integrates across a Turborepo monorepo — the part hardest to figure out from scratch.
128
+
129
+ ### DB Package (`packages/db/`)
130
+
131
+ **`package.json` exports:**
132
+ ```json
133
+ {
134
+ "exports": {
135
+ "./auth": {
136
+ "import": "./dist/auth/schema.js",
137
+ "types": "./dist/auth/schema.d.ts"
138
+ }
139
+ }
140
+ }
141
+ ```
142
+
143
+ **Scripts:**
144
+ ```json
145
+ {
146
+ "auth:generate": "drizzle-kit generate --config ./drizzle/auth/drizzle.config.ts",
147
+ "auth:migrate": "drizzle-kit generate --config ./drizzle/auth/drizzle.config.ts && drizzle-kit migrate --config ./drizzle/auth/drizzle.config.ts",
148
+ "drizzle:studio:auth": "drizzle-kit studio --config ./drizzle/auth/drizzle.config.ts"
149
+ }
150
+ ```
151
+
152
+ **Dependencies:** `drizzle-orm`, `dotenv` | **Dev:** `drizzle-kit`, `pg`
153
+
154
+ **File structure:**
155
+ ```
156
+ packages/db/
157
+ ├── src/auth/schema.ts # Auto-generated by Better Auth CLI
158
+ ├── drizzle/auth/
159
+ │ ├── drizzle.config.ts # Drizzle Kit config
160
+ │ └── *.sql # Generated migrations
161
+ └── .env # AUTH_DATABASE_URL
162
+ ```
163
+
164
+ ### Server Package (e.g. `apis/server/`)
165
+
166
+ **Key script:**
167
+ ```json
168
+ {
169
+ "auth:generate": "npx @better-auth/cli@latest generate --config ./src/auth/index.ts --output ../../packages/db/src/auth/schema.ts --yes"
170
+ }
171
+ ```
172
+
173
+ This reads your `betterAuth()` config and auto-generates the Drizzle schema in the DB package.
174
+
175
+ **Dependencies:** `better-auth`, `drizzle-orm`, `pg`, `@t3-oss/env-core`, `zod`, `resend`
176
+ **Workspace dep:** `{{SCOPE}}/db` (for importing `{{SCOPE}}/db/auth`)
177
+
178
+ ### Web App (`apps/web/`)
179
+
180
+ **Import alias** (in `package.json`):
181
+ ```json
182
+ {
183
+ "imports": {
184
+ "#auth/*": "./auth/*"
185
+ }
186
+ }
187
+ ```
188
+
189
+ This enables clean imports: `import { authClient } from "#auth/client"`.
190
+
191
+ **`next.config.ts` — API proxy rewrites (for standalone server mode):**
192
+ ```typescript
193
+ async rewrites() {
194
+ const apiUrl = process.env.NEXT_PUBLIC_API_URL;
195
+ if (!apiUrl || apiUrl.includes("localhost")) return [];
196
+ return [{ source: "/api/:path*", destination: `${apiUrl}/api/:path*` }];
197
+ }
198
+ ```
199
+
200
+ In production, this proxies `/api/*` through the frontend domain so cookies work across frontend/backend on the same domain.
201
+
202
+ ### Root `package.json`
203
+
204
+ ```json
205
+ {
206
+ "scripts": {
207
+ "auth:generate": "turbo run auth:generate --filter={{SCOPE}}/server --no-cache",
208
+ "auth:migrate": "turbo run auth:migrate --filter={{SCOPE}}/db --no-cache",
209
+ "auth:setup": "pnpm run auth:generate && pnpm run auth:migrate"
210
+ }
211
+ }
212
+ ```
213
+
214
+ ### `turbo.json`
215
+
216
+ ```json
217
+ {
218
+ "tasks": {
219
+ "auth:generate": { "cache": false },
220
+ "auth:migrate": { "cache": false }
221
+ }
222
+ }
223
+ ```
224
+
225
+ ### `pnpm-workspace.yaml`
226
+
227
+ Ensure `apis/*` is included (for standalone server mode):
228
+ ```yaml
229
+ packages:
230
+ - "apps/*"
231
+ - "apis/*"
232
+ - "packages/*"
233
+ - "config/*"
234
+ ```
235
+
236
+ ---
237
+
238
+ ## Single-Repo Adaptation
239
+
240
+ For standalone applications (Next.js, Node, Bun) without a monorepo, auth integrates directly into the app. The core auth patterns are identical — only file placement and wiring differ.
241
+
242
+ ### Path Mapping
243
+
244
+ | Aspect | Monorepo | Single-Repo |
245
+ |--------|----------|-------------|
246
+ | Auth server config | `apis/server/src/auth/index.ts` | `src/lib/auth/index.ts` |
247
+ | Auth DB connection | `apis/server/src/db/auth.ts` | `src/lib/db/auth.ts` |
248
+ | Auth middleware | `apis/server/src/middleware/auth.ts` | `src/middleware/auth.ts` or Next.js middleware |
249
+ | Auth handler | Hono at `apis/server/` | Next.js API route `app/api/auth/[...all]/route.ts` |
250
+ | Client code | `apps/web/auth/` | `src/auth/` or `auth/` |
251
+ | Import alias | `#auth/*` → `./auth/*` | `#auth/*` or `@/auth/*` |
252
+ | DB schema | `packages/db/src/auth/schema.ts` | `src/db/auth/schema.ts` |
253
+ | Drizzle config | `packages/db/drizzle/auth/drizzle.config.ts` | `drizzle/auth/drizzle.config.ts` |
254
+ | Workspace deps | `"@scope/db": "workspace:*"` | N/A (direct imports) |
255
+ | CORS config | Required (separate frontend/backend) | Not needed (same origin) |
256
+ | API proxy rewrites | `next.config.ts` rewrites for Hono mode | Not needed with API routes |
257
+ | Scripts | Turbo wrappers (`turbo run auth:generate --filter=...`) | Direct CLI calls in root `package.json` |
258
+
259
+ ### Single-Repo Server Framework
260
+
261
+ Single-repo projects typically use **Next.js API routes** directly instead of a standalone Hono server. This simplifies the setup:
262
+
263
+ - No CORS configuration needed (same origin)
264
+ - No API proxy rewrites needed
265
+ - No separate server package
266
+ - Auth handler mounts at `app/api/auth/[...all]/route.ts`
267
+
268
+ ### Single-Repo Scripts
269
+
270
+ ```json
271
+ {
272
+ "scripts": {
273
+ "auth:generate": "npx @better-auth/cli@latest generate --config ./src/lib/auth/index.ts --output ./src/db/auth/schema.ts --yes",
274
+ "auth:migrate": "drizzle-kit generate --config ./drizzle/auth/drizzle.config.ts && drizzle-kit migrate --config ./drizzle/auth/drizzle.config.ts",
275
+ "auth:setup": "pnpm auth:generate && pnpm auth:migrate"
276
+ }
277
+ }
278
+ ```
279
+
280
+ ### What Stays the Same
281
+
282
+ All core auth patterns are project-type-agnostic:
283
+ - Better Auth library + configuration shape
284
+ - Drizzle adapter pattern
285
+ - Session management (cookie cache, expiry, refresh)
286
+ - Organization plugin (if used)
287
+ - Client-side auth hooks (SWR mutations)
288
+ - Form schemas (Zod validation)
289
+ - Route protection middleware logic (cookie check)
290
+ - Auth flows (sign-in, sign-up, verify, forgot-password, reset, invite)
291
+ - Email sending pattern
292
+
293
+ ---
294
+
295
+ ## Platform Integration
296
+
297
+ Better Auth is framework-agnostic. Here's how to mount it on different platforms.
298
+
299
+ ### Hono (Recommended)
300
+
301
+ ```typescript
302
+ import { Hono } from "hono";
303
+ import { cors } from "hono/cors";
304
+ import auth from "./auth/index.js";
305
+ import { env } from "./env.js";
306
+
307
+ const app = new Hono();
308
+
309
+ // CORS for auth routes
310
+ app.use("/api/auth/*", cors({
311
+ origin: [env.FRONTEND_DOMAIN],
312
+ allowHeaders: ["Content-Type", "Authorization"],
313
+ allowMethods: ["POST", "GET", "OPTIONS"],
314
+ exposeHeaders: ["Content-Length"],
315
+ maxAge: 600,
316
+ credentials: true,
317
+ }));
318
+
319
+ // Mount Better Auth handler
320
+ app.on(["POST", "GET"], "/api/auth/*", (c) => {
321
+ return auth.handler(c.req.raw);
322
+ });
323
+ ```
324
+
325
+ ### Next.js API Routes
326
+
327
+ Create `app/api/auth/[...all]/route.ts`:
328
+ ```typescript
329
+ import { toNextJsHandler } from "better-auth/next-js";
330
+ import auth from "@/lib/auth";
331
+
332
+ export const { POST, GET } = toNextJsHandler(auth);
333
+ ```
334
+
335
+ See: https://www.better-auth.com/docs/integrations/next-js
336
+
337
+ ### Other Platforms
338
+
339
+ - **Express:** https://www.better-auth.com/docs/integrations/express
340
+ - **Fastify:** https://www.better-auth.com/docs/integrations/fastify
341
+ - **SvelteKit:** https://www.better-auth.com/docs/integrations/svelte-kit
342
+ - **Nuxt:** https://www.better-auth.com/docs/integrations/nuxt
343
+
344
+ ---
345
+
346
+ ## Auth Flows
347
+
348
+ Each flow below documents the page pattern, user flow, form fields, API calls, and error handling. When scaffolding, generate fresh UI matching the target project's design system using these specifications.
349
+
350
+ ### Visual Layout Pattern
351
+
352
+ Auth pages use a **split-screen layout**:
353
+ - **Left side:** Form content (logo, heading, form fields, action buttons, navigation links)
354
+ - **Right side:** Decorative illustration or branding image (hidden on mobile, visible on `lg:` breakpoint)
355
+ - Max form width: `max-w-sm` on desktop
356
+ - Pages wrap in `Suspense` with skeleton loading states (pulse-animated placeholder rectangles)
357
+
358
+ ### Shared UI Components
359
+
360
+ These patterns appear across multiple auth pages:
361
+ - **AuthCard** — centered card with: optional icon (in circular muted background), title, description, children, footer
362
+ - **PasswordInput** — password field with show/hide toggle (eye/eye-slash icon), optional "Must be at least 8 characters" hint
363
+ - **ContinueWith** — pill-shaped button with icon + text for social sign-in
364
+ - **BackLink** — left-arrow link for navigation to previous page
365
+ - **Loading skeletons** — animated pulse rectangles matching form field heights
366
+
367
+ ---
368
+
369
+ ### Flow 1: Auth Landing (`/auth`)
370
+
371
+ **Purpose:** Entry point — choose sign-in method.
372
+
373
+ **Layout:** Logo centered, heading, subtitle, two buttons with "or" divider, terms footer.
374
+
375
+ **Elements:**
376
+ 1. App logo
377
+ 2. Heading: "Get started with {{APP_NAME}}"
378
+ 3. Subtitle: descriptive tagline
379
+ 4. "Continue with Google" button → calls `authClient.signIn.social({ provider: "google", callbackURL })`
380
+ 5. "or" divider
381
+ 6. "Continue with Email" link → navigates to `/auth/email`
382
+ 7. Terms of Service + Privacy Policy links
383
+
384
+ **Behavior:**
385
+ - If user is already authenticated and visits `/auth` (exact), redirect to `/`
386
+ - Google button respects `returnUrl` search param for post-auth redirect
387
+
388
+ ---
389
+
390
+ ### Flow 2: Email Entry (`/auth/email`)
391
+
392
+ **Purpose:** Enter email, then route to sign-in (existing account) or sign-up (new account).
393
+
394
+ **Form fields:**
395
+ | Field | Type | Validation |
396
+ |---|---|---|
397
+ | email | email | Required, valid email |
398
+
399
+ **API call:** Server action that checks if email exists in auth database (timing-safe to prevent enumeration).
400
+
401
+ **Behavior:**
402
+ - On submit: check email → if exists, redirect to `/auth/signin?email=...` → if not, redirect to `/auth/signup?email=...`
403
+ - Also shows "Continue with Google" as alternate option
404
+ - Back link → `/auth`
405
+
406
+ ---
407
+
408
+ ### Flow 3: Sign In (`/auth/signin`)
409
+
410
+ **Purpose:** Email/password authentication.
411
+
412
+ **Form fields:**
413
+ | Field | Type | Validation | Notes |
414
+ |---|---|---|---|
415
+ | email | email | Required, valid email | Pre-filled + read-only if passed via URL params |
416
+ | password | password | Required | Show/hide toggle via PasswordInput |
417
+
418
+ **API call:** `authClient.signIn.email({ email, password })`
419
+
420
+ **Error handling:**
421
+ | Error Code | Message | Action |
422
+ |---|---|---|
423
+ | `USER_NOT_FOUND` | "No account found with this email" | Shows "Create an account instead" link |
424
+ | `INVALID_CREDENTIALS` | "No account found with this email" | — |
425
+ | `EMAIL_NOT_VERIFIED` | "Please verify your email first" | Redirects to `/auth/verify-email?email=...` |
426
+ | `UNKNOWN` | Original error message | — |
427
+
428
+ **Success:** `router.push(returnUrl || "/")`
429
+
430
+ **Links:** "Forgot password?" → `/auth/forgot-password`, "Don't have an account? Sign up" → `/auth/signup`
431
+
432
+ ---
433
+
434
+ ### Flow 4: Sign Up (`/auth/signup`)
435
+
436
+ **Purpose:** Create new account with email/password.
437
+
438
+ **Form fields:**
439
+ | Field | Type | Validation | Notes |
440
+ |---|---|---|---|
441
+ | email | email | Required, valid email | Pre-filled if passed via URL |
442
+ | name | text | Required, trimmed | Auto-focused if email pre-filled |
443
+ | password | password | Required, min 8 chars | Shows "Must be at least 8 characters" hint |
444
+ | confirmPassword | password | Must match password | — |
445
+
446
+ **API call:** `authClient.signUp.email({ email, name, password })`
447
+
448
+ **Error handling:**
449
+ | Error Code | Message | Action |
450
+ |---|---|---|
451
+ | `EMAIL_EXISTS` | "An account with this email already exists" | Shows "Sign in instead" link |
452
+
453
+ **Success:** Redirect to `/auth/verify-email?email=...`
454
+
455
+ **Links:** "Already have an account? Sign in" → `/auth/signin`
456
+
457
+ ---
458
+
459
+ ### Flow 5: Email Verification (`/auth/verify-email`)
460
+
461
+ **Two pages:**
462
+
463
+ **5a. Check your email** (`/auth/verify-email?email=...`)
464
+ - Uses **AuthCard** layout with envelope icon
465
+ - Title: "Check your email"
466
+ - Description: "We sent a verification link to {email}"
467
+ - "Resend verification email" button with 60-second cooldown timer
468
+ - "Wrong email? Start over" footer link → `/auth`
469
+ - **API call (resend):** `authClient.sendVerificationEmail({ email })`
470
+
471
+ **5b. Token verification** (`/auth/verify-email/[token]`)
472
+ - Automatically verifies on load via `authClient.verifyEmail({ query: { token } })`
473
+ - States: loading (spinner), success (checkmark + "Email verified" + sign-in link), expired (warning + "Request new link"), invalid (error + "Request new link")
474
+ - Uses `useSWR` (not mutation) — runs once, no retry
475
+
476
+ ---
477
+
478
+ ### Flow 6: Forgot Password (`/auth/forgot-password`)
479
+
480
+ **Form fields:**
481
+ | Field | Type | Validation |
482
+ |---|---|---|
483
+ | email | email | Required, valid email |
484
+
485
+ **API call:** `authClient.forgetPassword({ email, redirectTo: "/auth/reset-password" })`
486
+
487
+ **Behavior:** Always redirects to `/auth/forgot-password/sent?email=...` regardless of whether email exists (prevents enumeration).
488
+
489
+ **Sent page** (`/auth/forgot-password/sent`):
490
+ - AuthCard with envelope icon
491
+ - "Check your email" title
492
+ - "We sent a password reset link to {email}"
493
+ - "Didn't receive the email? Check your spam folder" hint
494
+ - Back link → `/auth/signin`
495
+
496
+ ---
497
+
498
+ ### Flow 7: Reset Password (`/auth/reset-password/[token]`)
499
+
500
+ **Form fields:**
501
+ | Field | Type | Validation |
502
+ |---|---|---|
503
+ | password | password | Required, min 8 chars (with hint) |
504
+ | confirmPassword | password | Must match password |
505
+
506
+ **API call:** `authClient.resetPassword({ token, newPassword })`
507
+
508
+ **Error handling:**
509
+ | Error Code | Page State | Display |
510
+ |---|---|---|
511
+ | `EXPIRED` | AuthCard with warning icon | "Link expired" + "Request new link" button → `/auth/forgot-password` |
512
+ | `INVALID` | AuthCard with error icon | "Invalid link" + "Request new link" button |
513
+
514
+ **Success:** AuthCard with checkmark icon, "Password updated" + "Sign in with new password" link → `/auth/signin`
515
+
516
+ ---
517
+
518
+ ### Flow 8: Accept Invitation (`/auth/invite/[token]`)
519
+
520
+ **Purpose:** Accept an organization invitation.
521
+
522
+ **States (sequential):**
523
+ 1. **Loading** — spinner while checking session
524
+ 2. **Not authenticated** — Card with "You've Been Invited", "Sign in to Accept" button → redirects to `/auth?returnUrl=/auth/invite/{token}`
525
+ 3. **Loading invitation** — spinner while fetching invitation details
526
+ 4. **Expired** — "Invitation Expired" card with "Go to Dashboard" button
527
+ 5. **Error/Invalid** — "Invalid Invitation" card with error message
528
+ 6. **Accepted** — "Welcome!" card with checkmark, auto-redirects to `/`
529
+ 7. **Main view** — Shows org name, role, invitee email in a bordered card, "Accept Invitation" button
530
+
531
+ **API calls:**
532
+ - `authClient.useSession()` — check if logged in
533
+ - `authClient.organization.getInvitation({ query: { id: token } })` — fetch invitation details
534
+ - `authClient.organization.acceptInvitation({ invitationId: token })` — accept
535
+ - `authClient.organization.setActive({ organizationId })` — switch to new org after accepting
536
+
537
+ ---
538
+
539
+ ### Flow 9: Onboarding — Create Organization (`/onboarding/create-organization`)
540
+
541
+ **Purpose:** After sign-up, create first organization/workspace.
542
+
543
+ **Guard:** `OnboardingRedirect` component checks `needsOnboarding` from org context. If user has zero orgs after loading, redirects here.
544
+
545
+ **Form fields:**
546
+ | Field | Type | Validation |
547
+ |---|---|---|
548
+ | name | text | Required, max 50 chars, trimmed |
549
+
550
+ **API call:** Uses `useCreateOrganization()` hook → `authClient.organization.create()` + `setActive()`
551
+
552
+ **Success:** `router.push("/")`
553
+
554
+ ---
555
+
556
+ ### Flow 10: Organization Switching
557
+
558
+ **Location:** Sidebar header dropdown.
559
+
560
+ **Elements:**
561
+ - Current org name + logo displayed in sidebar header
562
+ - Dropdown shows all user's organizations with checkmark on active
563
+ - Click to switch: `authClient.organization.setActive({ organizationId })`
564
+ - "Create Organization" option at bottom → opens dialog
565
+
566
+ **Create Organization Dialog:**
567
+ - Form with name field (same schema as onboarding)
568
+ - Uses `useOrganizationActions().createOrganization()`
569
+
570
+ ---
571
+
572
+ ### Flow 11: Team Management (`/settings/members`)
573
+
574
+ **Purpose:** View members, invite new ones, manage roles, remove members.
575
+
576
+ **Page layout:**
577
+ - Header: "Members" title + member count badge + "Invite" button (admin+ only, via `useHasPermission("admin")`)
578
+ - Member list: sorted by role (owner → admin → member)
579
+ - Pending invitations section below members
580
+
581
+ **Member row:** Avatar/initials, name, email, role badge. Dropdown menu with "Change Role" and "Remove" actions (permission-gated).
582
+
583
+ **Invitation row:** Email, role badge, "pending" status badge, expiry date, cancel button.
584
+
585
+ **Dialogs:**
586
+ - **InviteMemberDialog** — email + role select (Admin/Member), calls `inviteMember()`
587
+ - **ChangeMemberRoleDialog** — role select, calls `updateMemberRole()`
588
+ - **RemoveMemberDialog** — confirmation, calls `removeMember()`
589
+ - **CancelInvitationDialog** — confirmation, calls `authClient.organization.cancelInvitation()`
590
+
591
+ **API calls:** All via `useOrganizationActions()` hooks + `authClient.organization.getFullOrganization()` for member/invitation data.
592
+
593
+ ---
594
+
595
+ ## Route Protection
596
+
597
+ ### Client-Side (Next.js Middleware)
598
+
599
+ Checks for `better-auth.session_token` or `__Secure-better-auth.session_token` cookie:
600
+ - **Public routes** (`/auth/*`): accessible without auth. Authenticated users on `/auth` (exact) redirected to `/`
601
+ - **Protected routes** (everything else): unauthenticated users redirected to `/auth?returnUrl=...`
602
+ - **Onboarding routes** (`/onboarding/*`): accessible if authenticated
603
+ - **Skipped**: `/_next/*`, `/api/*`, files with `.` in path
604
+
605
+ ### Server-Side (Auth Middleware)
606
+
607
+ The framework-agnostic core:
608
+ ```typescript
609
+ const sessionData = await auth.api.getSession({ headers: request.headers });
610
+ ```
611
+
612
+ Two middleware levels:
613
+ - `requireAuth()` — validates session, attaches `{ user, session }` to context
614
+ - `requireOrganization()` — validates session + checks `session.activeOrganizationId`
615
+
616
+ ### Protected Layout Pattern
617
+
618
+ ```
619
+ (protected)/layout.tsx
620
+ → OrganizationProvider (wraps with org context)
621
+ → OnboardingRedirect (checks needsOnboarding, redirects if no orgs)
622
+ → children
623
+ ```
624
+
625
+ ---
626
+
627
+ ## File Manifest
628
+
629
+ | File | Purpose | Monorepo Target | Single-Repo Target |
630
+ |---|---|---|---|
631
+ | `files/server/auth.ts` | Better Auth config | `apis/server/src/auth/index.ts` | `src/lib/auth/index.ts` |
632
+ | `files/server/auth-db.ts` | Drizzle DB connection for auth | `apis/server/src/db/auth.ts` | `src/lib/db/auth.ts` |
633
+ | `files/server/middleware.ts` | Session validation middleware | `apis/server/src/middleware/auth.ts` | `src/middleware/auth.ts` |
634
+ | `files/server/env.ts` | Auth env validation (t3-env) | `apis/server/src/env.ts` | `env.ts` (merge into existing) |
635
+ | `files/client/auth-client.ts` | `createAuthClient()` setup | `apps/web/auth/client.ts` | `src/auth/client.ts` |
636
+ | `files/client/context/organization.ts` | Organization context | `apps/web/auth/context/organization.ts` | `src/auth/context/organization.ts` |
637
+ | `files/client/hooks/use-organization.ts` | Org consumption hooks | `apps/web/auth/hooks/use-organization.ts` | `src/auth/hooks/use-organization.ts` |
638
+ | `files/client/hooks/use-has-permission.ts` | Role-based permission | `apps/web/auth/hooks/use-has-permission.ts` | `src/auth/hooks/use-has-permission.ts` |
639
+ | `files/client/hooks/use-create-organization.ts` | SWR mutation for org | `apps/web/auth/hooks/use-create-organization.ts` | `src/auth/hooks/use-create-organization.ts` |
640
+ | `files/client/schema/auth.ts` | Shared Zod schemas | `apps/web/auth/schema/auth.ts` | `src/auth/schema/auth.ts` |
641
+ | `files/client/schema/organization.ts` | Org form schemas | `apps/web/auth/schema/organization.ts` | `src/auth/schema/organization.ts` |
642
+ | `files/client/types/organization.ts` | Organization types | `apps/web/auth/types/organization.ts` | `src/auth/types/organization.ts` |
643
+ | `files/db/auth-schema.ts` | Drizzle schema (auto-generated) | `packages/db/src/auth/schema.ts` | `src/db/auth/schema.ts` |
644
+ | `files/db/drizzle.config.ts` | Drizzle Kit config for auth | `packages/db/drizzle/auth/drizzle.config.ts` | `drizzle/auth/drizzle.config.ts` |
645
+ | `files/middleware/route-protection.ts` | Next.js route guard | `apps/web/middleware.ts` | `middleware.ts` |
646
+
647
+ ---
648
+
649
+ ## Integration Steps
650
+
651
+ When scaffolding this blueprint into a new project, follow this order:
652
+
653
+ ### Phase 1: Database
654
+
655
+ 1. Create `packages/db/src/auth/` directory
656
+ 2. Copy `files/db/drizzle.config.ts` → `packages/db/drizzle/auth/drizzle.config.ts`
657
+ 3. Add `"./auth"` export to `packages/db/package.json`
658
+ 4. Add `auth:generate`, `auth:migrate`, `drizzle:studio:auth` scripts to `packages/db/package.json`
659
+ 5. Install deps: `drizzle-orm`, `dotenv` + dev: `drizzle-kit`, `pg`
660
+ 6. Set `AUTH_DATABASE_URL` in `packages/db/.env`
661
+
662
+ ### Phase 2: Server
663
+
664
+ 7. Adapt `files/server/auth.ts` — replace `{{APP_NAME}}`, `{{EMAIL_FROM}}`, configure providers/plugins
665
+ 8. Adapt `files/server/auth-db.ts` — set correct import path for DB package
666
+ 9. Adapt `files/server/env.ts` — merge auth vars into your server's env validation
667
+ 10. Mount auth handler on your server (see Platform Integration)
668
+ 11. Copy `files/server/middleware.ts` — adapt for your server framework if not Hono
669
+ 12. Set env vars: `BETTER_AUTH_URL`, `BETTER_AUTH_SECRET`, `FRONTEND_DOMAIN`, social provider credentials, `RESEND_API_KEY`
670
+ 13. Run `npx @better-auth/cli@latest generate` to produce the Drizzle schema
671
+ 14. Run `drizzle-kit generate && drizzle-kit migrate` to create database tables
672
+
673
+ ### Phase 3: Client
674
+
675
+ 15. Install deps: `better-auth`, `swr`, `react-hook-form`, `@hookform/resolvers`, `zod`
676
+ 16. Copy `files/client/auth-client.ts` → `apps/web/auth/client.ts`
677
+ 17. Add `"#auth/*": "./auth/*"` to `apps/web/package.json` imports
678
+ 18. Copy `files/client/context/`, `hooks/`, `schema/`, `types/` → `apps/web/auth/`
679
+ 19. Set env vars: `NEXT_PUBLIC_API_URL`, `NEXT_PUBLIC_FRONTEND_URL`
680
+
681
+ ### Phase 4: Route Protection
682
+
683
+ 20. Copy `files/middleware/route-protection.ts` → `apps/web/middleware.ts`
684
+ 21. Add API proxy rewrites to `next.config.ts` (for standalone server mode only)
685
+
686
+ ### Phase 5: UI
687
+
688
+ 22. Build auth pages following the Auth Flows documentation above
689
+ 23. Build protected layout with `OrganizationProvider` + `OnboardingRedirect`
690
+ 24. Build onboarding flow (create organization page)
691
+ 25. Build team management pages (if using org plugin)
692
+
693
+ ### Phase 6: Project Scripts (monorepo)
694
+
695
+ > Skip this phase for single-repo projects — use Phase 6-alt instead.
696
+
697
+ 26. Add `auth:generate` script to server package (runs `@better-auth/cli generate`)
698
+ 27. Add `auth:generate`, `auth:migrate`, `auth:setup` to root `package.json` (Turbo wrappers)
699
+ 28. Add `auth:generate` and `auth:migrate` tasks to `turbo.json` (both `cache: false`)
700
+
701
+ ### Phase 6-alt: Project Scripts (single-repo)
702
+
703
+ > Use this phase instead of Phase 6 for standalone applications.
704
+
705
+ 26. Add `auth:generate`, `auth:migrate`, `auth:setup` scripts to root `package.json` (direct CLI calls — see Single-Repo Scripts in the Single-Repo Adaptation section)
706
+ 27. Add auth dependencies to root `package.json`
707
+
708
+ ---
709
+
710
+ ## Maintenance Hooks
711
+
712
+ Rules that should be added to the target project's CLAUDE.md during scaffolding. These ensure auth stays consistent after code changes.
713
+
714
+ ### Schema & Config Hooks
715
+
716
+ | When you... | Then run... | Why |
717
+ |---|---|---|
718
+ | Modify the Better Auth server config (`auth.ts`) — plugins, providers, fields, session settings | `pnpm auth:generate` then `pnpm auth:migrate` | Config changes can alter the database schema |
719
+ | Add or remove a social provider | `pnpm auth:generate` | Provider changes affect the account schema |
720
+ | Change organization plugin settings (roles, limits, membership config) | `pnpm auth:generate` then `pnpm auth:migrate` | Org config changes affect member/invitation tables |
721
+ | Edit `auth-schema.ts` manually | `pnpm auth:migrate` | Manual schema edits need migrations applied |
722
+ | Update the `better-auth` package version | `pnpm auth:generate` then check for schema diff, migrate if needed | New versions may include schema changes |
723
+
724
+ ### Environment Hooks
725
+
726
+ | When you... | Then run... | Why |
727
+ |---|---|---|
728
+ | Add a new env var to server `env.ts` | Add it to all relevant `.env` files and verify with `pnpm dev` | Missing env vars crash at startup due to t3-env validation |
729
+ | Add a new `NEXT_PUBLIC_` env var | Add to `apps/web/.env.local` and restart dev server | Next.js requires restart to pick up new env vars |
730
+ | Change `BETTER_AUTH_URL` or `FRONTEND_DOMAIN` | Update CORS config and verify OAuth callback URLs still work | Mismatched URLs break OAuth flows and cookie sharing |
731
+
732
+ ### Validation Checks
733
+
734
+ After any auth-related changes, verify:
735
+ - Dev server starts without errors: `pnpm dev`
736
+ - Auth API responds: visit `{BETTER_AUTH_URL}/api/auth/ok`
737
+ - If schema changed: `pnpm drizzle:studio:auth` shows expected tables
738
+ - If middleware changed: unauthenticated requests to protected routes redirect to `/auth`
739
+ - If org plugin changed: verify org creation and member invitation flows
740
+
741
+ ### Condensed Rules for CLAUDE.md
742
+
743
+ This is the version that gets injected into the target project's CLAUDE.md during scaffolding:
744
+ ```markdown
745
+ ### auth-better-auth maintenance
746
+ - After modifying Better Auth server config, plugins, or providers: run `pnpm auth:generate && pnpm auth:migrate`
747
+ - After updating `better-auth` package: run `pnpm auth:generate`, check for schema changes, migrate if needed
748
+ - After adding/changing env vars: update all `.env` files and verify `pnpm dev` starts clean
749
+ - After changing `BETTER_AUTH_URL` or `FRONTEND_DOMAIN`: verify CORS config and OAuth callback URLs
750
+ - After any auth changes: verify `{BETTER_AUTH_URL}/api/auth/ok` responds
751
+ - Never edit the auto-generated auth schema directly — modify the Better Auth config and regenerate
752
+ ```
753
+
754
+ ---
755
+
756
+ ## Removing the Organization Plugin
757
+
758
+ If your app doesn't need multi-tenancy:
759
+
760
+ 1. **Server:** Remove `organization()` from plugins array in `auth.ts`. Remove `currentOrganizationId` from `user.additionalFields`.
761
+ 2. **Client:** Remove `organizationClient()` from `createAuthClient()` plugins. Delete `context/`, `hooks/use-organization.ts`, `hooks/use-has-permission.ts`, `hooks/use-create-organization.ts`, `schema/organization.ts`, `types/organization.ts`.
762
+ 3. **Middleware:** Remove `requireOrganization()` — use only `requireAuth()`.
763
+ 4. **UI:** Remove onboarding flow, org switcher, team management. Simplify protected layout (no `OrganizationProvider` or `OnboardingRedirect`).
764
+ 5. **Schema:** The auto-generated schema will not include `organization`, `member`, or `invitation` tables.
765
+
766
+ ---
767
+
768
+ ## Claude Code Skills (Optional)
769
+
770
+ The official Better Auth skills provide additional context during scaffolding. They are **not required** — this BLUEPRINT.md contains everything needed. Install them for quick-reference guides on configuration and best practices:
771
+
772
+ ```bash
773
+ npx skillsadd better-auth/skills
774
+ ```
775
+
776
+ This installs all Better Auth skills from [github.com/better-auth/skills](https://github.com/better-auth/skills), including:
777
+
778
+ - **`/create-auth`** — Decision tree for new vs existing projects, framework-specific route handlers, database adapter setup, plugin configuration, security checklist
779
+ - **`/best-practices`** — Core config options, session management strategies, cookie cache modes, security hardening, hooks, type safety, common gotchas
780
+ - **`/organization`** — Organization plugin setup and multi-tenancy patterns
781
+ - **`/twoFactor`** — Two-factor authentication implementation
782
+
783
+ ---
784
+
785
+ ## References
786
+
787
+ - **Better Auth Docs:** https://www.better-auth.com/docs
788
+ - **Better Auth LLMs.txt:** https://www.better-auth.com/llms.txt
789
+ - **Better Auth GitHub:** https://github.com/better-auth/better-auth
790
+ - **Better Auth Plugins:** https://www.better-auth.com/docs/plugins
791
+ - **Drizzle ORM Docs:** https://orm.drizzle.team/docs/overview
792
+ - **Drizzle PostgreSQL:** https://orm.drizzle.team/docs/get-started/postgresql-new
793
+ - **t3-env Docs:** https://env.t3.gg
794
+ - **Resend Docs:** https://resend.com/docs