Scaffold and implement authentication in TypeScript/JavaScript apps using Better Auth. Detect frameworks, configure database adapters, set up route handlers, add OAuth providers, and create auth UI pages. Use when users want to add login, sign-up, or authentication to a new or existing project with Better Auth.
npx skills add https://github.com/better-auth/skills --skill create-auth
Guide for adding authentication to TypeScript/JavaScript applications using Better Auth.
For code examples and syntax, see better-auth.com/docs.
Before writing any code, gather requirements by scanning the project and asking the user structured questions. This ensures the implementation matches their needs.
Analyze the codebase to auto-detect:
next.config, svelte.config, nuxt.config, astro.config, vite.config, or Express/Hono entry files.prisma/schema.prisma, drizzle.config.ts, package.json deps (pg, postgres, @neondatabase/serverless, mysql2, better-sqlite3, mongoose, mongodb). If drizzle.config.ts exists, read its dialect field to determine the DB type (e.g., "postgresql" → Drizzle + Postgres). Also check which Drizzle driver is installed (drizzle-orm/node-postgres → pg, drizzle-orm/postgres-js → postgres, drizzle-orm/neon-http → Neon).next-auth, lucia, clerk, supabase/auth, firebase/auth) in package.json or imports.pnpm-lock.yaml, yarn.lock, bun.lockb, or package-lock.json.Use what you find to pre-fill defaults and skip questions you can already answer.
Use the AskQuestion tool to ask the user all applicable questions in a single call. Skip any question you already have a confident answer for from the scan. Group them under a title like "Auth Setup Planning".
Questions to ask:
allow_multiple: trueallow_multiple: trueallow_multiple: trueallow_multiple: true10. Auth UI style (always ask)
After collecting answers, present a concise implementation plan as a markdown checklist. Example:
## Auth Implementation Plan
- **Framework:** Next.js (App Router)
- **Database:** PostgreSQL via Prisma
- **Auth methods:** Email/password, Google OAuth, GitHub OAuth
- **Plugins:** 2FA, Organizations, Email verification
- **UI:** Custom forms
### Steps
1. Install `better-auth` and `@better-auth/cli`
2. Create `lib/auth.ts` with server config
3. Create `lib/auth-client.ts` with React client
4. Set up route handler at `app/api/auth/[...all]/route.ts`
5. Configure Prisma adapter and generate schema
6. Add Google & GitHub OAuth providers
7. Enable `twoFactor` and `organization` plugins
8. Set up email verification handler
9. Run migrations
10. Create sign-in / sign-up pages
Ask the user to confirm the plan before proceeding to Phase 2.
Only proceed here after the user confirms the plan from Phase 1.
Follow the decision tree below, guided by the answers collected above.
Is this a new/empty project?
├─ YES → New project setup
│ 1. Install better-auth (+ scoped packages per plan)
│ 2. Create auth.ts with all planned config
│ 3. Create auth-client.ts with framework client
│ 4. Set up route handler
│ 5. Set up environment variables
│ 6. Run CLI migrate/generate
│ 7. Add plugins from plan
│ 8. Create auth UI pages
│
├─ MIGRATING → Migration from existing auth
│ 1. Audit current auth for gaps
│ 2. Plan incremental migration
│ 3. Install better-auth alongside existing auth
│ 4. Migrate routes, then session logic, then UI
│ 5. Remove old auth library
│ 6. See migration guides in docs
│
└─ ADDING → Add auth to existing project
1. Analyze project structure
2. Install better-auth
3. Create auth config matching plan
4. Add route handler
5. Run schema migrations
6. Integrate into existing pages
7. Add planned plugins and features
At the end of implementation, guide users thoroughly on remaining next steps (e.g., setting up OAuth app credentials, deploying env vars, testing flows).
Core: npm install better-auth
Scoped packages (as needed):
| Package | Use case |
|---------|----------|
| @better-auth/passkey | WebAuthn/Passkey auth |
| @better-auth/sso | SAML/OIDC enterprise SSO |
| @better-auth/stripe | Stripe payments |
| @better-auth/scim | SCIM user provisioning |
| @better-auth/expo | React Native/Expo |
BETTER_AUTH_SECRET=<32+ chars, generate with: openssl rand -base64 32>
BETTER_AUTH_URL=http://localhost:3000
DATABASE_URL=<your database connection string>
Add OAuth secrets as needed: GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, GOOGLE_CLIENT_ID, etc.
Location: lib/auth.ts or src/lib/auth.ts
Minimal config needs:
database - Connection or adapteremailAndPassword: { enabled: true } - For email/password authStandard config adds:
socialProviders - OAuth providers (google, github, etc.)emailVerification.sendVerificationEmail - Email verification handleremailAndPassword.sendResetPassword - Password reset handlerFull config adds:
plugins - Array of feature pluginssession - Expiry, cookie cache settingsaccount.accountLinking - Multi-provider linkingrateLimit - Rate limiting configExport types: export type Session = typeof auth.$Infer.Session
Import by framework:
| Framework | Import |
|-----------|--------|
| React/Next.js | better-auth/react |
| Vue | better-auth/vue |
| Svelte | better-auth/svelte |
| Solid | better-auth/solid |
| Vanilla JS | better-auth/client |
Client plugins go in createAuthClient({ plugins: [...] }).
Common exports: signIn, signUp, signOut, useSession, getSession
| Framework | File | Handler |
|-----------|------|---------|
| Next.js App Router | app/api/auth/[...all]/route.ts | toNextJsHandler(auth) → export { GET, POST } |
| Next.js Pages | pages/api/auth/[...all].ts | toNextJsHandler(auth) → default export |
| Express | Any file | app.all("/api/auth/*", toNodeHandler(auth)) |
| SvelteKit | src/hooks.server.ts | svelteKitHandler(auth) |
| SolidStart | Route file | solidStartHandler(auth) |
| Hono | Route file | auth.handler(c.req.raw) |
Next.js Server Components: Add nextCookies() plugin to auth config.
| Adapter | Command |
|---------|---------|
| Built-in Kysely | npx @better-auth/cli@latest migrate (applies directly) |
| Prisma | npx @better-auth/cli@latest generate --output prisma/schema.prisma then npx prisma migrate dev |
| Drizzle (dev) | npx @better-auth/cli@latest generate --output src/db/auth-schema.ts then npx drizzle-kit push |
| Drizzle (prod) | npx @better-auth/cli@latest generate --output src/db/auth-schema.ts then npx drizzle-kit generate then npx drizzle-kit migrate |
> Note: drizzle-kit push skips migration files and is only safe for development. Use drizzle-kit generate + drizzle-kit migrate in production.
Re-run after adding plugins.
| Database | Setup |
|----------|-------|
| SQLite | Pass better-sqlite3 or bun:sqlite instance directly |
| PostgreSQL | Pass pg.Pool instance directly |
| MySQL | Pass mysql2 pool directly |
| Prisma | prismaAdapter(prisma, { provider: "postgresql" }) from better-auth/adapters/prisma |
| Drizzle (pg) | drizzleAdapter(db, { provider: "pg" }) from better-auth/adapters/drizzle |
| Drizzle (mysql) | drizzleAdapter(db, { provider: "mysql" }) from better-auth/adapters/drizzle |
| Drizzle (sqlite) | drizzleAdapter(db, { provider: "sqlite" }) from better-auth/adapters/drizzle |
| MongoDB | mongodbAdapter(db) from better-auth/adapters/mongodb |
Before using drizzleAdapter, initialize the db instance:
// Option 1: node-postgres (pg)
import { drizzle } from "drizzle-orm/node-postgres"
import { Pool } from "pg"
import * as schema from "./auth-schema"
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
export const db = drizzle(pool, { schema })
// Option 2: postgres.js
import { drizzle } from "drizzle-orm/postgres-js"
import postgres from "postgres"
import * as schema from "./auth-schema"
const client = postgres(process.env.DATABASE_URL!)
export const db = drizzle(client, { schema })
// Option 3: Neon serverless
import { drizzle } from "drizzle-orm/neon-http"
import { neon } from "@neondatabase/serverless"
import * as schema from "./auth-schema"
const sql = neon(process.env.DATABASE_URL!)
export const db = drizzle(sql, { schema })
Then pass to Better Auth:
import { betterAuth } from "better-auth"
import { drizzleAdapter } from "better-auth/adapters/drizzle"
import { db } from "./db"
export const auth = betterAuth({
database: drizzleAdapter(db, { provider: "pg" }),
// ...
})
drizzle.config.ts)Required for drizzle-kit commands to find your schema:
import { defineConfig } from "drizzle-kit"
export default defineConfig({
schema: "./src/db/auth-schema.ts",
out: "./drizzle",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
})
| Plugin | Server Import | Client Import | Purpose |
|--------|---------------|---------------|---------|
| twoFactor | better-auth/plugins | twoFactorClient | 2FA with TOTP/OTP |
| organization | better-auth/plugins | organizationClient | Teams/orgs |
| admin | better-auth/plugins | adminClient | User management |
| bearer | better-auth/plugins | - | API token auth |
| openAPI | better-auth/plugins | - | API docs |
| passkey | @better-auth/passkey | passkeyClient | WebAuthn |
| sso | @better-auth/sso | - | Enterprise SSO |
Plugin pattern: Server plugin + client plugin + run migrations.
Sign in flow:
signIn.email({ email, password }) or signIn.social({ provider, callbackURL })error in responseSession check (client): useSession() hook returns { data: session, isPending }
Session check (server): auth.api.getSession({ headers: await headers() })
Protected routes: Check session, redirect to /sign-in if null.
BETTER_AUTH_SECRET set (32+ chars)advanced.useSecureCookies: true in productiontrustedOrigins configuredaccount.accountLinking reviewed| Issue | Fix |
|-------|-----|
| "Secret not set" | Add BETTER_AUTH_SECRET env var |
| "Invalid Origin" | Add domain to trustedOrigins |
| Cookies not setting | Check baseURL matches domain; enable secure cookies in prod |
| OAuth callback errors | Verify redirect URIs in provider dashboard |
| Type errors after adding plugin | Re-run CLI generate/migrate |
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
Automatically creates user-facing changelogs from git commits by analyzing commit history, categorizing changes, and transforming technical commits into clear, customer-friendly release notes. Turns hours of manual changelog writing into minutes of automated generation.
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling
Use when starting feature work that needs isolation from current workspace or before executing implementation plans - creates isolated git worktrees with smart directory selection and safety verification
Take better-auth/create-auth from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npm, npx.
Without those the skill loads but fails at the first command.