plugins/better-auth/skills/better-auth/SKILL.md
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.
npx skillsauth add secondsky/claude-skills better-authInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Status: 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 moretools
Use for Bun runtime, bunfig.toml, watch/hot modes, env vars, CLI flags, and module resolution.
data-ai
Use when working with Redis in Bun (ioredis, Upstash), caching, pub/sub, session storage, or key-value operations.
development
Use when building server-rendered React with Bun, including streaming SSR, hydration, renderToString, or custom SSR without a framework.
databases
Bun package manager commands (install, add, remove, update), workspaces, lockfiles, npm/yarn/pnpm migration. Use for dependency management with Bun.