Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Configure Better Auth server and client, set up database adapters, manage sessions, add plugins, and handle environment variables. Use when users mention Better Auth, betterauth, auth.ts, or need to set up TypeScript authentication with email/password, OAuth, or plugin configuration.
.claude/skills/aiskillstore-better-auth-best-practices/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | -6% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 50% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 56% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 67% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 121% | 0% |
Always consult better-auth.com/docs for code examples and latest API.
npm install better-authBETTER_AUTH_SECRET and BETTER_AUTH_URLauth.ts with database + confignpx @better-auth/cli@latest migratenpx @better-auth/cli@latest generate --output src/db/auth-schema.ts then npx drizzle-kit push (dev) or npx drizzle-kit generate && npx drizzle-kit migrate (prod)npx @better-auth/cli@latest generate --output prisma/schema.prisma then npx prisma migrate devGET /api/auth/ok — should return { status: "ok" }BETTER_AUTH_SECRET - Encryption secret (min 32 chars). Generate: openssl rand -base64 32BETTER_AUTH_URL - Base URL (e.g., https://example.com)Only define baseURL/secret in config if env vars are NOT set.
CLI looks for auth.ts in: ./, ./lib, ./utils, or under ./src. Use --config for custom path.
npx @better-auth/cli@latest migrate - Apply schema (built-in adapter)npx @better-auth/cli@latest generate - Generate schema for Prisma/Drizzlenpx @better-auth/cli mcp --cursor - Add MCP to AI toolsRe-run after adding/changing plugins.
| Option | Notes | |--------|-------| | appName | Optional display name | | baseURL | Only if BETTER_AUTH_URL not set | | basePath | Default /api/auth. Set / for root. | | secret | Only if BETTER_AUTH_SECRET not set | | database | Required for most features. See adapters docs. | | secondaryStorage | Redis/KV for sessions & rate limits | | emailAndPassword | { enabled: true } to activate | | socialProviders | { google: { clientId, clientSecret }, ... } | | plugins | Array of plugins | | trustedOrigins | CSRF whitelist |
Direct connections: Pass pg.Pool, mysql2 pool, better-sqlite3, or bun:sqlite instance. For Postgres, also supports postgres (postgres.js) and @neondatabase/serverless.
ORM adapters: Import from better-auth/adapters/drizzle, better-auth/adapters/prisma, better-auth/adapters/mongodb.
Drizzle provider values: "pg" (PostgreSQL), "mysql" (MySQL), "sqlite" (SQLite). Must match the driver used.
Critical: Better Auth uses adapter model names, NOT underlying table names. If Prisma model is User mapping to table users, use modelName: "user" (Prisma reference), not "users".
Storage priority:
secondaryStorage defined → sessions go there (not DB)session.storeSessionInDatabase: true to also persist to DBcookieCache → fully stateless modeCookie cache strategies:
compact (default) - Base64url + HMAC. Smallest.jwt - Standard JWT. Readable but signed.jwe - Encrypted. Maximum security.Key options: session.expiresIn (default 7 days), session.updateAge (refresh interval), session.cookieCache.maxAge, session.cookieCache.version (change to invalidate all sessions).
User: user.modelName, user.fields (column mapping), user.additionalFields, user.changeEmail.enabled (disabled by default), user.deleteUser.enabled (disabled by default).
Account: account.modelName, account.accountLinking.enabled, account.storeAccountCookie (for stateless OAuth).
Required for registration: email and name fields.
emailVerification.sendVerificationEmail - Must be defined for verification to workemailVerification.sendOnSignUp / sendOnSignIn - Auto-send triggersemailAndPassword.sendResetPassword - Password reset email handlerIn advanced:
useSecureCookies - Force HTTPS cookiesdisableCSRFCheck - ⚠️ Security riskdisableOriginCheck - ⚠️ Security risk crossSubDomainCookies.enabled - Share cookies across subdomainsipAddress.ipAddressHeaders - Custom IP headers for proxiesdatabase.generateId - Custom ID generation or "serial"/"uuid"/falseRate limiting: rateLimit.enabled, rateLimit.window, rateLimit.max, rateLimit.storage ("memory" | "database" | "secondary-storage").
Endpoint hooks: hooks.before / hooks.after - Array of { matcher, handler }. Use createAuthMiddleware. Access ctx.path, ctx.context.returned (after), ctx.context.session.
Database hooks: databaseHooks.user.create.before/after, same for session, account. Useful for adding default values or post-creation actions.
Hook context (ctx.context): session, secret, authCookies, password.hash()/verify(), adapter, internalAdapter, generateId(), tables, baseURL.
Import from dedicated paths for tree-shaking:
import { twoFactor } from "better-auth/plugins/two-factor"NOT from "better-auth/plugins".
Popular plugins: twoFactor, organization, passkey, magicLink, emailOtp, username, phoneNumber, admin, apiKey, bearer, jwt, multiSession, sso, oauthProvider, oidcProvider, openAPI, genericOAuth.
Client plugins go in createAuthClient({ plugins: [...] }).
Import from: better-auth/client (vanilla), better-auth/react, better-auth/vue, better-auth/svelte, better-auth/solid.
Key methods: signUp.email(), signIn.email(), signIn.social(), signOut(), useSession(), getSession(), revokeSession(), revokeSessions().
Infer types: typeof auth.$Infer.Session, typeof auth.$Infer.Session.user.
For separate client/server projects: createAuthClient<typeof auth>().
drizzleAdapter(db, ...) requires a db instance from drizzle(). See create-auth skill for setup examples (node-postgres, postgres.js, Neon).drizzle-kit commands require a drizzle.config.ts pointing to the generated schema file and DB credentials.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 27,915 | 15,676 | -44% | 1 | 1 | 0% | 4,581 | 4,311 | -6% | 0 | 0 | — |
case-02 | fail→pass | 22,059 | 13,890 | -37% | 1 | 1 | 0% | 3,156 | 4,743 | +50% | 0 | 0 | — |
case-03 | fail→pass | 12,798 | 12,055 | -6% | 1 | 1 | 0% | 2,681 | 4,193 | +56% | 0 | 0 | — |
case-04 | pass→pass | 12,370 | 12,480 | +1% | 1 | 1 | 0% | 1,411 | 3,697 | +162% | 0 | 0 | — |
case-05 | pass→pass | 8,670 | 3,109 | -64% | 1 | 1 | 0% | 682 | 2,570 | +277% | 0 | 0 | — |
case-06 | pass→pass | 9,096 | 5,001 | -45% | 1 | 1 | 0% | 1,108 | 2,881 | +160% | 0 | 0 | — |
case-07 | pass→pass | 16,918 | 8,065 | -52% | 1 | 1 | 0% | 1,346 | 2,607 | +94% | 0 | 0 | — |
case-08 | pass→pass | 7,028 | 9,547 | +36% | 1 | 1 | 0% | 1,052 | 2,648 | +152% | 0 | 0 | — |
case-09 | pass→pass | 15,353 | 11,878 | -23% | 1 | 1 | 0% | 1,735 | 2,510 | +45% | 0 | 0 | — |
case-10 | pass→pass | 15,230 | 8,789 | -42% | 1 | 1 | 0% | 1,509 | 2,332 | +55% | 0 | 0 | — |
case-15 | fail→pass | 18,313 | 18,900 | +3% | 1 | 1 | 0% | 2,267 | 3,797 | +67% | 0 | 0 | — |
case-11 | pass→pass | 15,931 | 12,582 | -21% | 1 | 1 | 0% | 2,502 | 3,844 | +54% | 0 | 0 | — |
case-12 | pass→pass | 23,325 | 22,622 | -3% | 1 | 1 | 0% | 2,215 | 3,896 | +76% | 0 | 0 | — |
case-13 | pass→pass | 25,582 | 8,794 | -66% | 1 | 1 | 0% | 1,189 | 2,685 | +126% | 0 | 0 | — |
case-14 | fail→pass | 13,995 | 12,046 | -14% | 1 | 1 | 0% | 1,083 | 2,390 | +121% | 0 | 0 | — |
case-16 | pass→pass | 43,085 | 16,326 | -62% | 1 | 1 | 0% | 2,379 | 3,956 | +66% | 0 | 0 | — |
case-17 | pass→pass | 11,847 | 21,360 | +80% | 1 | 1 | 0% | 1,104 | 2,790 | +153% | 0 | 0 | — |
case-18 | pass→pass | 15,052 | 4,354 | -71% | 1 | 1 | 0% | 1,484 | 2,576 | +74% | 0 | 0 | — |
case-19 | fail→pass | 14,071 | 13,278 | -6% | 1 | 1 | 0% | 1,502 | 2,283 | +52% | 0 | 0 | — |
case-20 | pass→pass | 21,514 | 17,812 | -17% | 1 | 1 | 0% | 1,051 | 2,555 | +143% | 0 | 0 | — |
case-21 | pass→pass | 27,329 | 10,526 | -61% | 1 | 1 | 0% | 1,734 | 2,491 | +44% | 0 | 0 | — |
case-22 | pass→pass | 12,859 | 17,466 | +36% | 1 | 1 | 0% | 1,434 | 3,377 | +135% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +27 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.