From better-auth
Skill for integrating Better Auth - comprehensive TypeScript authentication framework for Cloudflare D1, Next.js, Nuxt, and 15+ frameworks. Use when adding auth, encountering D1 adapter errors, or implementing OAuth/2FA/RBAC features.
How this skill is triggered — by the user, by Claude, or both
Slash command
/better-auth:better-authThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
**Status**: Production Ready
assets/auth-flow-diagram.mdreferences/advanced-features.mdreferences/cloudflare-worker-drizzle.tsreferences/cloudflare-worker-kysely.tsreferences/configuration-guide.mdreferences/database-schema.tsreferences/databases/mongodb.mdreferences/databases/mysql.mdreferences/databases/postgresql.mdreferences/error-catalog.mdreferences/framework-comparison.mdreferences/frameworks/api-frameworks.mdreferences/frameworks/expo-mobile.mdreferences/frameworks/nextjs.mdreferences/frameworks/nuxt.mdreferences/frameworks/remix.mdreferences/frameworks/sveltekit.mdreferences/integrations/electron.mdreferences/migration-guide-1.4.0.mdreferences/migration-guide-1.5.0.mdStatus: Production Ready
Last Updated: 2026-04-08
Package: [email protected] (ESM-only)
Dependencies: Drizzle ORM or Kysely (required for D1 complex use cases; D1 native support available in v1.5+)
Option 1: Drizzle ORM (Recommended)
bun add better-auth drizzle-orm drizzle-kit
Option 2: Kysely
bun add better-auth kysely @noxharmonium/kysely-d1
better-auth v1.4.0+ is ESM-only. Ensure:
package.json:
{
"type": "module"
}
Upgrading from v1.3.x? Load references/migration-guide-1.4.0.md
Upgrading from v1.4.x? Load references/migration-guide-1.5.0.md
v1.5.0+: D1 is now natively supported. Pass your D1 binding directly:
// ✅ SIMPLEST - D1 native (v1.5.0+)
import { betterAuth } from "better-auth";
const auth = betterAuth({
database: env.DB, // D1 binding, auto-detected
});
For complex schemas, use Drizzle ORM:
// ✅ RECOMMENDED for complex schemas - Drizzle
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { drizzle } from "drizzle-orm/d1";
const auth = betterAuth({
database: drizzleAdapter(drizzle(env.DB, { schema }), { provider: "sqlite" }),
});
// ❌ WRONG - This doesn't exist
import { d1Adapter } from 'better-auth/adapters/d1'
1. Create D1 Database:
wrangler d1 create my-app-db
2. Define Schema (src/db/schema.ts):
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const user = sqliteTable("user", {
id: text().primaryKey(),
name: text().notNull(),
email: text().notNull().unique(),
emailVerified: integer({ mode: "boolean" }).notNull().default(false),
image: text(),
});
export const session = sqliteTable("session", {
id: text().primaryKey(),
userId: text().notNull().references(() => user.id, { onDelete: "cascade" }),
token: text().notNull(),
expiresAt: integer({ mode: "timestamp" }).notNull(),
});
// See references/database-schema.ts for complete schema
3. Initialize Auth (src/auth.ts):
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { drizzle } from "drizzle-orm/d1";
import * as schema from "./db/schema";
export function createAuth(env: { DB: D1Database; BETTER_AUTH_SECRET: string }) {
const db = drizzle(env.DB, { schema });
return betterAuth({
baseURL: env.BETTER_AUTH_URL,
secret: env.BETTER_AUTH_SECRET,
database: drizzleAdapter(db, { provider: "sqlite" }),
emailAndPassword: { enabled: true },
});
}
4. Create Worker (src/index.ts):
import { Hono } from "hono";
import { createAuth } from "./auth";
const app = new Hono<{ Bindings: Env }>();
app.all("/api/auth/*", async (c) => {
const auth = createAuth(c.env);
return auth.handler(c.req.raw);
});
export default app;
5. Deploy:
bunx drizzle-kit generate
wrangler d1 migrations apply my-app-db --remote
wrangler deploy
For code examples and syntax, always consult better-auth.com/docs.
Is this a new/empty project?
├─ YES → New project setup
│ 1. Identify framework (Next.js, Nuxt, Workers, etc.)
│ 2. Choose database (D1, PostgreSQL, MongoDB, MySQL)
│ 3. Install better-auth + Drizzle/Kysely
│ 4. Create auth.ts + auth-client.ts
│ 5. Set up route handler (see Quick Start above)
│ 6. Run migrations (Drizzle Kit for D1)
│ 7. Add features via plugins (2FA, organizations, etc.)
│
└─ NO → Does project have existing auth?
├─ YES → Migration/enhancement
│ • Audit current auth for gaps
│ • Plan incremental migration
│ • See references/framework-comparison.md for migration guides
│
└─ NO → Add auth to existing project
1. Analyze project structure
2. Install better-auth + adapter
3. Create auth config (see Quick Start)
4. Add route handler to existing routes
5. Run schema migrations
6. Integrate into existing pages/components
✅ Use better-auth/minimal + adapter packages for smallest bundle (v1.5+)
✅ Use npx auth migrate and npx auth generate for CLI commands (v1.5+)
✅ Set BETTER_AUTH_SECRET via wrangler secret put
✅ Configure CORS with credentials: true
✅ Match OAuth callback URLs exactly (no trailing slash)
✅ Apply migrations to local D1 before wrangler dev
✅ Use camelCase column names in schema
❌ Use d1Adapter (doesn't exist)
❌ Forget CORS credentials or mismatch OAuth URLs
❌ Use snake_case columns without CamelCasePlugin
❌ Skip local migrations or hardcode secrets
❌ Leave sendVerificationEmail unimplemented
API Key Plugin Moved:
- import { apiKey } from "better-auth/plugins";
+ import { apiKey } from "@better-auth/api-key";
Schema: userId → referenceId, new configId field.
After Hooks: Database after-hooks now run post-transaction (not inside it).
Deprecated APIs Removed: Adapter → DBAdapter, InferUser/InferSession removed, @better-auth/core/utils split into subpath exports.
Load references/migration-guide-1.5.0.md when upgrading from <1.5.0
Session Freshness: freshAge now uses createdAt (not updatedAt). Sessions may require re-auth more frequently for sensitive operations.
SAML Security: InResponseTo validation is default ON. Opt out with saml: { enableInResponseToValidation: false }.
OIDC Provider Deprecated: Use @better-auth/oauth-provider instead.
npx auth init/migrate/generate/upgrade@better-auth/oauth-provider (MCP-ready)@better-auth/electron for desktop apps@better-auth/i18n for error translationscode in error responsesLoad references/v1.5-features.md for detailed implementation guides.
mode: "insensitive" on adapter queriesLoad references/v1.6-features.md for detailed implementation guides.
| Variable | Purpose | Example |
|---|---|---|
BETTER_AUTH_SECRET | Encryption secret (min 32 chars) | Generate: openssl rand -base64 32 |
BETTER_AUTH_URL | Base URL | https://example.com or http://localhost:8787 |
DATABASE_URL | Database connection (optional for D1) | PostgreSQL/MySQL connection string |
Note: Only define baseURL/secret in config if env vars are NOT set.
| Command | Purpose |
|---|---|
npx auth init | Interactive project scaffolding |
npx auth migrate | Run database migrations |
npx auth generate | Generate auth schema |
npx auth generate --adapter drizzle | Adapter-specific schema |
npx auth upgrade | Upgrade to latest version |
bunx drizzle-kit generate | D1: Use this to generate Drizzle migrations |
wrangler d1 migrations apply DB_NAME | D1: Use this to apply migrations |
Re-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 (min 32 chars) |
database | Required for most features. Use drizzleAdapter() or Kysely for D1 |
secondaryStorage | Redis/KV for sessions & rate limits |
emailAndPassword | { enabled: true } to activate |
socialProviders | { google: { clientId, clientSecret }, ... } |
plugins | Array of plugins (import from dedicated paths) |
trustedOrigins | CSRF whitelist for cross-origin requests |
Import from dedicated packages (extracted in v1.5+):
import { twoFactor } from "better-auth/plugins/two-factor"
import { organization } from "better-auth/plugins/organization"
import { passkey } from "@better-auth/passkey" // Separate package
import { apiKey } from "@better-auth/api-key" // Separate package (v1.5+)
import { sso } from "@better-auth/sso" // Separate package (v1.5+)
import { i18n } from "@better-auth/i18n" // Separate package (v1.5+)
import { oauthProvider } from "@better-auth/oauth-provider" // Separate package (v1.5+)
Core plugins (still in better-auth/plugins): twoFactor, organization, admin, anonymous, emailOTP, magicLink, phone-number, multi-session, custom-session.
Problem: Trying to use non-existent d1Adapter
Solution: Use drizzleAdapter or Kysely instead (see Quick Start above)
Problem: better-auth migrate doesn't work with D1
Solution: Use bunx drizzle-kit generate then wrangler d1 migrations apply
Problem: Database uses email_verified but better-auth expects emailVerified
Solution: Use camelCase in schema or add CamelCasePlugin to Kysely
Problem: Access-Control-Allow-Origin errors, cookies not sent
Solution: Configure CORS with credentials: true and correct origins
Problem: Social sign-in fails with "redirect_uri_mismatch"
Solution: Ensure exact match: https://yourdomain.com/api/auth/callback/google
Load references/error-catalog.md for all 15 errors with detailed solutions.
When: Basic authentication without social providers Quick Pattern:
// Client
await authClient.signIn.email({
email: "[email protected]",
password: "password123",
});
// Server - enable in config
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
}
Load: references/setup-guide.md → Step 5
When: Allow users to sign in with social accounts Supported: Google, GitHub, Microsoft, Apple, Discord, TikTok, Twitch, Spotify, LinkedIn, Slack, Reddit, Facebook, Twitter/X, Patreon, Vercel, Kick, and 30+ more. Quick Pattern:
// Client
await authClient.signIn.social({
provider: "google",
callbackURL: "/dashboard",
});
// Server config
socialProviders: {
google: {
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
scope: ["openid", "email", "profile"],
},
}
Load: references/setup-guide.md → Step 5
When: Need to verify user is authenticated Quick Pattern:
app.get("/api/protected", async (c) => {
const auth = createAuth(c.env);
const session = await auth.api.getSession({
headers: c.req.raw.headers,
});
if (!session) {
return c.json({ error: "Unauthorized" }, 401);
}
return c.json({ data: "protected", user: session.user });
});
Load: references/cloudflare-worker-drizzle.ts
When: Building SaaS with teams/organizations
Load: references/advanced-features.md → Organizations & Teams
When: Need extra security with 2FA/TOTP
Load: references/advanced-features.md → Two-Factor Authentication
Load references/setup-guide.md when:
Load references/error-catalog.md when:
Load references/advanced-features.md when:
Load references/v1.5-features.md when:
Load references/v1.6-features.md when:
Load references/migration-guide-1.5.0.md when:
userId → referenceId)InferUser/InferSession type errors@better-auth/core/utils importsLoad references/plugins/sso.md when:
Load references/plugins/test-utils.md when:
Load references/integrations/electron.md when:
Load references/cloudflare-worker-drizzle.ts when:
Load references/cloudflare-worker-kysely.ts when:
Load references/database-schema.ts when:
Load references/react-client-hooks.tsx when:
Load references/configuration-guide.md when:
Load references/framework-comparison.md when:
Load references/migration-guide-1.4.0.md when:
forgetPassword errors or ESM issuesLoad references/v1.4-features.md when:
Load references/nextjs/README.md when:
Load references/nextjs/postgres-example.ts when:
Load references/frameworks/nextjs.md when:
Load references/frameworks/nuxt.md when:
Load references/frameworks/remix.md when:
Load references/frameworks/sveltekit.md when:
Load references/frameworks/api-frameworks.md when:
Load references/frameworks/expo-mobile.md when:
Load references/databases/postgresql.md when:
Load references/databases/mongodb.md when:
Load references/databases/mysql.md when:
Load references/plugins/authentication.md when:
Load references/plugins/enterprise.md when:
Load references/plugins/api-tokens.md when:
Load references/plugins/payments.md when:
Load references/plugins/sso.md when:
Load references/plugins/test-utils.md when:
Load references/integrations/electron.md when:
Quick Config (ESM-only in v1.4.0+):
export const auth = betterAuth({
baseURL: env.BETTER_AUTH_URL,
secret: env.BETTER_AUTH_SECRET,
database: drizzleAdapter(db, { provider: "sqlite" }),
});
Load references/configuration-guide.md for:
Create auth client (src/lib/auth-client.ts):
import { createAuthClient } from "better-auth/client";
export const authClient = createAuthClient({
baseURL: import.meta.env.VITE_API_URL || "http://localhost:8787",
});
Use in React:
import { authClient } from "@/lib/auth-client";
export function UserProfile() {
const { data: session, isPending } = authClient.useSession();
if (isPending) return <div>Loading...</div>;
if (!session) return <div>Not authenticated</div>;
return (
<div>
<p>Welcome, {session.user.email}</p>
<button onClick={() => authClient.signOut()}>Sign Out</button>
</div>
);
}
Required:
better-auth@^1.6.0 - Core authentication framework (ESM-only)Choose ONE adapter (optional with D1 native in v1.5+):
drizzle-orm@^0.44.7 + drizzle-kit@^0.31.7 (recommended for complex schemas)kysely@^0.28.8 + @noxharmonium/kysely-d1@^0.4.0 (alternative)@better-auth/drizzle-adapter + better-auth/minimal (smallest bundle, v1.5+)Optional:
@cloudflare/workers-types - TypeScript types for Workershono@^4.0.0 - Web framework for routing@better-auth/passkey - Passkey/WebAuthn plugin@better-auth/api-key - API key auth with org support@better-auth/sso - SSO/SAML/OIDC production plugin@better-auth/electron - Electron desktop auth@better-auth/i18n - Error message translations@better-auth/oauth-provider - OAuth 2.1 authorization serverThis skill focuses on Cloudflare Workers + D1. better-auth also supports:
Frameworks (18 total): Next.js, Nuxt, Remix, SvelteKit, Astro, Express, NestJS, Fastify, Elysia, Expo, and more.
Databases (9 adapters): PostgreSQL, MongoDB, MySQL, Prisma, MS SQL, and others.
Additional Plugins: Anonymous auth, Email OTP, JWT, Multi-Session, OAuth 2.1 Provider, Test Utils, SCIM, payment integrations (Stripe, Polar), Device Authorization.
For non-Cloudflare setups, load the appropriate framework or database reference file, or consult the official docs: https://better-auth.com/docs
Load references/framework-comparison.md for:
Verified working repositories (all use Drizzle or Kysely):
Note: Check each repo's better-auth version. Repos on v1.3.x need v1.4.0+ migration (see references/migration-guide-1.4.0.md). None use a direct d1Adapter - all require Drizzle/Kysely.
When installing authentication packages, follow supply chain security best practices — auth libraries are high-value targets for supply chain attacks:
npm config set ignore-scripts true (or Bun: disabled by default)socket package score npm <pkg> or use socket npm install <pkg> to check packagesLoad the dependency-upgrade skill for full security configuration including Socket CLI integration, cooldown setup, lockfile validation, and CI enforcement.
"type": "module" in package.json) - v1.4.0+ requirednpx auth CLI (not @better-auth/cli) - v1.5.0+@better-auth/api-key (not better-auth/plugins) for API keys - v1.5.0+Questions? Issues?
references/error-catalog.md for all 15 errors and solutionsreferences/setup-guide.md for complete 8-step setupreferences/advanced-features.md for 2FA, organizations, and moreGuides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.
Manages knowledge base ingestion, sync, and retrieval across local files, MCP memory, vector stores, and Git repos. Use for saving, organizing, deduplicating, or searching knowledge.
npx claudepluginhub midego1/claude-skills --plugin better-auth