better-auth

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

better-auth - D1 Adapter & Error Prevention Guide

better-auth - D1适配器与问题预防指南

Package: better-auth@1.4.0 (Nov 22, 2025) Breaking Changes: ESM-only (v1.4.0), Multi-team table changes (v1.3), D1 requires Drizzle/Kysely (no direct adapter)

包版本:better-auth@1.4.0(2025年11月22日) 破坏性变更:仅支持ESM(v1.4.0)、多团队表结构变更(v1.3)、D1需搭配Drizzle/Kysely(无直接适配器)

⚠️ CRITICAL: D1 Adapter Requirement

⚠️ 重要提示:D1适配器要求

better-auth DOES NOT have
d1Adapter()
. You MUST use:
  • Drizzle ORM (recommended):
    drizzleAdapter(db, { provider: "sqlite" })
  • Kysely:
    new Kysely({ dialect: new D1Dialect({ database: env.DB }) })
See Issue #1 below for details.

better-auth 没有
d1Adapter()
方法。你必须使用:
  • Drizzle ORM(推荐):
    drizzleAdapter(db, { provider: "sqlite" })
  • Kysely
    new Kysely({ dialect: new D1Dialect({ database: env.DB }) })
详情见下方问题1。

What's New in v1.4.0 (Nov 22, 2025)

v1.4.0版本新特性(2025年11月22日)

Major Features:
  • Stateless session management - Sessions without database storage
  • ESM-only package ⚠️ Breaking: CommonJS no longer supported
  • JWT key rotation - Automatic key rotation for enhanced security
  • SCIM provisioning - Enterprise user provisioning protocol
  • @standard-schema/spec - Replaces ZodType for validation
  • CaptchaFox integration - Built-in CAPTCHA support
  • Automatic server-side IP detection
  • Cookie-based account data storage
  • Multiple passkey origins support
  • RP-Initiated Logout endpoint (OIDC)

主要功能
  • 无状态会话管理 - 无需数据库存储会话
  • 仅支持ESM包 ⚠️ 破坏性变更:不再支持CommonJS
  • JWT密钥轮换 - 自动轮换密钥以增强安全性
  • SCIM配置 - 企业用户配置协议
  • @standard-schema/spec - 替代ZodType用于验证
  • CaptchaFox集成 - 内置验证码支持
  • 自动服务器端IP检测
  • 基于Cookie的账户数据存储
  • 多Passkey源支持
  • RP发起的登出端点(OIDC)

What's New in v1.3 (July 2025)

v1.3版本新特性(2025年7月)

Major Features:
  • SSO with SAML 2.0 - Enterprise single sign-on (moved to separate
    @better-auth/sso
    package)
  • Multi-team support ⚠️ Breaking:
    teamId
    removed from member table, new
    teamMembers
    table required
  • Additional fields - Custom fields for organization/member/invitation models
  • Performance improvements and bug fixes

主要功能
  • SAML 2.0单点登录(SSO) - 企业级单点登录(已迁移至独立包
    @better-auth/sso
  • 多团队支持 ⚠️ 破坏性变更:成员表中移除
    teamId
    ,新增
    teamMembers
  • 自定义字段 - 为组织/成员/邀请模型添加自定义字段
  • 性能优化与Bug修复

Alternative: Kysely Adapter Pattern

替代方案:Kysely适配器模式

If you prefer Kysely over Drizzle:
File:
src/auth.ts
typescript
import { betterAuth } from "better-auth";
import { Kysely, CamelCasePlugin } from "kysely";
import { D1Dialect } from "kysely-d1";

type Env = {
  DB: D1Database;
  BETTER_AUTH_SECRET: string;
  // ... other env vars
};

export function createAuth(env: Env) {
  return betterAuth({
    secret: env.BETTER_AUTH_SECRET,

    // Kysely with D1Dialect
    database: {
      db: new Kysely({
        dialect: new D1Dialect({
          database: env.DB,
        }),
        plugins: [
          // CRITICAL: Required if using Drizzle schema with snake_case
          new CamelCasePlugin(),
        ],
      }),
      type: "sqlite",
    },

    emailAndPassword: {
      enabled: true,
    },

    // ... other config
  });
}
Why CamelCasePlugin?
If your Drizzle schema uses
snake_case
column names (e.g.,
email_verified
), but better-auth expects
camelCase
(e.g.,
emailVerified
), the
CamelCasePlugin
automatically converts between the two.

如果你偏好Kysely而非Drizzle:
文件
src/auth.ts
typescript
import { betterAuth } from "better-auth";
import { Kysely, CamelCasePlugin } from "kysely";
import { D1Dialect } from "kysely-d1";

type Env = {
  DB: D1Database;
  BETTER_AUTH_SECRET: string;
  // ... 其他环境变量
};

export function createAuth(env: Env) {
  return betterAuth({
    secret: env.BETTER_AUTH_SECRET,

    // 搭配D1Dialect的Kysely
    database: {
      db: new Kysely({
        dialect: new D1Dialect({
          database: env.DB,
        }),
        plugins: [
          // 重要提示:如果Drizzle schema使用snake_case,必须添加此插件
          new CamelCasePlugin(),
        ],
      }),
      type: "sqlite",
    },

    emailAndPassword: {
      enabled: true,
    },

    // ... 其他配置
  });
}
为什么需要CamelCasePlugin?
如果你的Drizzle schema使用
snake_case
列名(例如
email_verified
),但better-auth期望
camelCase
(例如
emailVerified
),CamelCasePlugin会自动在两种命名规范之间转换。

Framework Integrations

框架集成

TanStack Start

TanStack Start

⚠️ CRITICAL: TanStack Start requires the
reactStartCookies
plugin to handle cookie setting properly.
typescript
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { reactStartCookies } from "better-auth/react-start";

export const auth = betterAuth({
  database: drizzleAdapter(db, { provider: "sqlite" }),
  plugins: [
    twoFactor(),
    organization(),
    reactStartCookies(), // ⚠️ MUST be LAST plugin
  ],
});
Why it's needed: TanStack Start uses a special cookie handling system. Without this plugin, auth functions like
signInEmail()
and
signUpEmail()
won't set cookies properly, causing authentication to fail.
Important: The
reactStartCookies
plugin must be the last plugin in the array.
API Route Setup (
/src/routes/api/auth/$.ts
):
typescript
import { auth } from '@/lib/auth'
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/api/auth/$')({
  server: {
    handlers: {
      GET: ({ request }) => auth.handler(request),
      POST: ({ request }) => auth.handler(request),
    },
  },
})

⚠️ 重要提示:TanStack Start需要
reactStartCookies
插件来正确处理Cookie设置。
typescript
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { reactStartCookies } from "better-auth/react-start";

export const auth = betterAuth({
  database: drizzleAdapter(db, { provider: "sqlite" }),
  plugins: [
    twoFactor(),
    organization(),
    reactStartCookies(), // ⚠️ 必须是数组中的最后一个插件
  ],
});
必要性:TanStack Start使用特殊的Cookie处理系统。如果没有此插件,
signInEmail()
signUpEmail()
等认证函数无法正确设置Cookie,导致认证失败。
注意
reactStartCookies
插件必须是数组中的最后一个插件
API路由配置
/src/routes/api/auth/$.ts
):
typescript
import { auth } from '@/lib/auth'
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/api/auth/$')({
  server: {
    handlers: {
      GET: ({ request }) => auth.handler(request),
      POST: ({ request }) => auth.handler(request),
    },
  },
})

Available Plugins (v1.3+)

可用插件(v1.3+)

Better Auth provides plugins for advanced authentication features:
PluginImportDescriptionDocs
OIDC Provider
better-auth/plugins
Build your own OpenID Connect provider (become an OAuth provider for other apps)📚
SSO
better-auth/plugins
Enterprise Single Sign-On with OIDC, OAuth2, and SAML 2.0 support📚
Stripe
better-auth/plugins
Payment and subscription management (stable as of v1.3+)📚
MCP
better-auth/plugins
Act as OAuth provider for Model Context Protocol (MCP) clients📚
Expo
better-auth/expo
React Native/Expo integration with secure cookie management📚

Better Auth提供用于高级认证功能的插件:
插件导入路径描述文档
OIDC Provider
better-auth/plugins
构建自己的OpenID Connect提供者(作为其他应用的OAuth提供者)📚
SSO
better-auth/plugins
支持OIDC、OAuth2和SAML 2.0的企业级单点登录📚
Stripe
better-auth/plugins
支付与订阅管理(v1.3+版本稳定)📚
MCP
better-auth/plugins
作为Model Context Protocol(MCP)客户端的OAuth提供者📚
Expo
better-auth/expo
React Native/Expo集成,支持安全Cookie管理📚

API Reference

API参考

Overview: What You Get For Free

概述:默认提供的功能

When you call
auth.handler()
, better-auth automatically exposes 80+ production-ready REST endpoints at
/api/auth/*
. Every endpoint is also available as a server-side method via
auth.api.*
for programmatic use.
This dual-layer API system means:
  • Clients (React, Vue, mobile apps) call HTTP endpoints directly
  • Server-side code (middleware, background jobs) uses
    auth.api.*
    methods
  • Zero boilerplate - no need to write auth endpoints manually
Time savings: Building this from scratch = ~220 hours. With better-auth = ~4-8 hours. 97% reduction.

调用
auth.handler()
后,better-auth会自动在
/api/auth/*
路径下暴露80+个生产就绪的REST端点。每个端点也可通过
auth.api.*
作为服务器端方法供程序化调用。
这种双层API系统意味着:
  • 客户端(React、Vue、移动应用)直接调用HTTP端点
  • 服务器端代码(中间件、后台任务)使用
    auth.api.*
    方法
  • 零样板代码 - 无需手动编写认证端点
时间节省:从零构建需要约220小时,使用better-auth仅需4-8小时,减少97%的开发时间

Auto-Generated HTTP Endpoints

自动生成的HTTP端点

All endpoints are automatically exposed at
/api/auth/*
when using
auth.handler()
.
使用
auth.handler()
时,所有端点会自动暴露在
/api/auth/*
路径下。

Core Authentication Endpoints

核心认证端点

EndpointMethodDescription
/sign-up/email
POSTRegister with email/password
/sign-in/email
POSTAuthenticate with email/password
/sign-out
POSTLogout user
/change-password
POSTUpdate password (requires current password)
/forget-password
POSTInitiate password reset flow
/reset-password
POSTComplete password reset with token
/send-verification-email
POSTSend email verification link
/verify-email
GETVerify email with token (
?token=<token>
)
/get-session
GETRetrieve current session
/list-sessions
GETGet all active user sessions
/revoke-session
POSTEnd specific session
/revoke-other-sessions
POSTEnd all sessions except current
/revoke-sessions
POSTEnd all user sessions
/update-user
POSTModify user profile (name, image)
/change-email
POSTUpdate email address
/set-password
POSTAdd password to OAuth-only account
/delete-user
POSTRemove user account
/list-accounts
GETGet linked authentication providers
/link-social
POSTConnect OAuth provider to account
/unlink-account
POSTDisconnect provider
端点请求方法描述
/sign-up/email
POST邮箱/密码注册
/sign-in/email
POST邮箱/密码登录
/sign-out
POST用户登出
/change-password
POST更新密码(需要当前密码)
/forget-password
POST启动密码重置流程
/reset-password
POST使用令牌完成密码重置
/send-verification-email
POST发送邮箱验证链接
/verify-email
GET使用令牌验证邮箱(
?token=<token>
/get-session
GET获取当前会话
/list-sessions
GET获取用户所有活跃会话
/revoke-session
POST终止指定会话
/revoke-other-sessions
POST终止除当前会话外的所有会话
/revoke-sessions
POST终止用户所有会话
/update-user
POST修改用户资料(姓名、头像)
/change-email
POST更新邮箱地址
/set-password
POST为仅OAuth登录的账户添加密码
/delete-user
POST删除用户账户
/list-accounts
GET获取关联的认证提供者
/link-social
POST关联OAuth提供者到账户
/unlink-account
POST断开提供者关联

Social OAuth Endpoints

社交OAuth端点

EndpointMethodDescription
/sign-in/social
POSTInitiate OAuth flow (provider specified in body)
/callback/:provider
GETOAuth callback handler (e.g.,
/callback/google
)
/get-access-token
GETRetrieve provider access token
Example OAuth flow:
typescript
// Client initiates
await authClient.signIn.social({
  provider: "google",
  callbackURL: "/dashboard",
});

// better-auth handles redirect to Google
// Google redirects back to /api/auth/callback/google
// better-auth creates session automatically

端点请求方法描述
/sign-in/social
POST启动OAuth流程(提供者在请求体中指定)
/callback/:provider
GETOAuth回调处理器(例如
/callback/google
/get-access-token
GET获取提供者访问令牌
OAuth流程示例
typescript
// 客户端发起请求
await authClient.signIn.social({
  provider: "google",
  callbackURL: "/dashboard",
});

// better-auth处理重定向到Google
// Google重定向回/api/auth/callback/google
// better-auth自动创建会话

Plugin Endpoints

插件端点

Two-Factor Authentication (2FA Plugin)
双因素认证(2FA插件)
typescript
import { twoFactor } from "better-auth/plugins";
EndpointMethodDescription
/two-factor/enable
POSTActivate 2FA for user
/two-factor/disable
POSTDeactivate 2FA
/two-factor/get-totp-uri
GETGet QR code URI for authenticator app
/two-factor/verify-totp
POSTValidate TOTP code from authenticator
/two-factor/send-otp
POSTSend OTP via email
/two-factor/verify-otp
POSTValidate email OTP
/two-factor/generate-backup-codes
POSTCreate recovery codes
/two-factor/verify-backup-code
POSTUse backup code for login
/two-factor/view-backup-codes
GETView current backup codes
typescript
import { twoFactor } from "better-auth/plugins";
端点请求方法描述
/two-factor/enable
POST为用户启用2FA
/two-factor/disable
POST停用2FA
/two-factor/get-totp-uri
GET获取用于认证器应用的二维码URI
/two-factor/verify-totp
POST验证来自认证器的TOTP码
/two-factor/send-otp
POST通过邮箱发送OTP
/two-factor/verify-otp
POST验证邮箱OTP
/two-factor/generate-backup-codes
POST创建恢复码
/two-factor/verify-backup-code
POST使用恢复码登录
/two-factor/view-backup-codes
GET查看当前恢复码
Organization Plugin (Multi-Tenant SaaS)
组织插件(多租户SaaS)
typescript
import { organization } from "better-auth/plugins";
Organizations (10 endpoints):
EndpointMethodDescription
/organization/create
POSTCreate organization
/organization/list
GETList user's organizations
/organization/get-full
GETGet complete org details
/organization/update
PUTModify organization
/organization/delete
DELETERemove organization
/organization/check-slug
GETVerify slug availability
/organization/set-active
POSTSet active organization context
Members (8 endpoints):
EndpointMethodDescription
/organization/list-members
GETGet organization members
/organization/add-member
POSTAdd member directly
/organization/remove-member
DELETERemove member
/organization/update-member-role
PUTChange member role
/organization/get-active-member
GETGet current member info
/organization/leave
POSTLeave organization
Invitations (7 endpoints):
EndpointMethodDescription
/organization/invite-member
POSTSend invitation email
/organization/accept-invitation
POSTAccept invite
/organization/reject-invitation
POSTReject invite
/organization/cancel-invitation
POSTCancel pending invite
/organization/get-invitation
GETGet invitation details
/organization/list-invitations
GETList org invitations
/organization/list-user-invitations
GETList user's pending invites
Teams (8 endpoints):
EndpointMethodDescription
/organization/create-team
POSTCreate team within org
/organization/list-teams
GETList organization teams
/organization/update-team
PUTModify team
/organization/remove-team
DELETERemove team
/organization/set-active-team
POSTSet active team context
/organization/list-team-members
GETList team members
/organization/add-team-member
POSTAdd member to team
/organization/remove-team-member
DELETERemove team member
Permissions & Roles (6 endpoints):
EndpointMethodDescription
/organization/has-permission
POSTCheck if user has permission
/organization/create-role
POSTCreate custom role
/organization/delete-role
DELETEDelete custom role
/organization/list-roles
GETList all roles
/organization/get-role
GETGet role details
/organization/update-role
PUTModify role permissions
typescript
import { organization } from "better-auth/plugins";
组织管理(10个端点):
端点请求方法描述
/organization/create
POST创建组织
/organization/list
GET列出用户所属组织
/organization/get-full
GET获取完整组织详情
/organization/update
PUT修改组织信息
/organization/delete
DELETE删除组织
/organization/check-slug
GET验证Slug可用性
/organization/set-active
POST设置活跃组织上下文
成员管理(8个端点):
端点请求方法描述
/organization/list-members
GET获取组织成员
/organization/add-member
POST直接添加成员
/organization/remove-member
DELETE移除成员
/organization/update-member-role
PUT修改成员角色
/organization/get-active-member
GET获取当前成员信息
/organization/leave
POST退出组织
邀请管理(7个端点):
端点请求方法描述
/organization/invite-member
POST发送邀请邮件
/organization/accept-invitation
POST接受邀请
/organization/reject-invitation
POST拒绝邀请
/organization/cancel-invitation
POST取消待处理邀请
/organization/get-invitation
GET获取邀请详情
/organization/list-invitations
GET列出组织邀请
/organization/list-user-invitations
GET列出用户的待处理邀请
团队管理(8个端点):
端点请求方法描述
/organization/create-team
POST在组织内创建团队
/organization/list-teams
GET列出组织团队
/organization/update-team
PUT修改团队信息
/organization/remove-team
DELETE删除团队
/organization/set-active-team
POST设置活跃团队上下文
/organization/list-team-members
GET列出团队成员
/organization/add-team-member
POST添加成员到团队
/organization/remove-team-member
DELETE移除团队成员
权限与角色管理(6个端点):
端点请求方法描述
/organization/has-permission
POST检查用户是否拥有权限
/organization/create-role
POST创建自定义角色
/organization/delete-role
DELETE删除自定义角色
/organization/list-roles
GET列出所有角色
/organization/get-role
GET获取角色详情
/organization/update-role
PUT修改角色权限
Admin Plugin
管理员插件
typescript
import { admin } from "better-auth/plugins";
EndpointMethodDescription
/admin/create-user
POSTCreate user as admin
/admin/list-users
GETList all users (with filters/pagination)
/admin/set-role
POSTAssign user role
/admin/set-user-password
POSTChange user password
/admin/update-user
PUTModify user details
/admin/remove-user
DELETEDelete user account
/admin/ban-user
POSTBan user account
/admin/unban-user
POSTUnban user
/admin/list-user-sessions
GETGet user's active sessions
/admin/revoke-user-session
DELETEEnd specific user session
/admin/revoke-user-sessions
DELETEEnd all user sessions
/admin/impersonate-user
POSTStart impersonating user
/admin/stop-impersonating
POSTEnd impersonation session
typescript
import { admin } from "better-auth/plugins";
端点请求方法描述
/admin/create-user
POST以管理员身份创建用户
/admin/list-users
GET列出所有用户(支持筛选/分页)
/admin/set-role
POST为用户分配角色
/admin/set-user-password
POST修改用户密码
/admin/update-user
PUT修改用户详情
/admin/remove-user
DELETE删除用户账户
/admin/ban-user
POST封禁用户账户
/admin/unban-user
POST解封用户
/admin/list-user-sessions
GET获取用户的活跃会话
/admin/revoke-user-session
DELETE终止用户的指定会话
/admin/revoke-user-sessions
DELETE终止用户的所有会话
/admin/impersonate-user
POST开始模拟用户
/admin/stop-impersonating
POST结束模拟会话
Other Plugin Endpoints
其他插件端点
Passkey Plugin (5 endpoints) - Docs:
  • /passkey/add
    ,
    /sign-in/passkey
    ,
    /passkey/list
    ,
    /passkey/delete
    ,
    /passkey/update
Magic Link Plugin (2 endpoints) - Docs:
  • /sign-in/magic-link
    ,
    /magic-link/verify
Username Plugin (2 endpoints) - Docs:
  • /sign-in/username
    ,
    /username/is-available
Phone Number Plugin (5 endpoints) - Docs:
  • /sign-in/phone-number
    ,
    /phone-number/send-otp
    ,
    /phone-number/verify
    ,
    /phone-number/request-password-reset
    ,
    /phone-number/reset-password
Email OTP Plugin (6 endpoints) - Docs:
  • /email-otp/send-verification-otp
    ,
    /email-otp/check-verification-otp
    ,
    /sign-in/email-otp
    ,
    /email-otp/verify-email
    ,
    /forget-password/email-otp
    ,
    /email-otp/reset-password
Anonymous Plugin (1 endpoint) - Docs:
  • /sign-in/anonymous
JWT Plugin (2 endpoints) - Docs:
  • /token
    (get JWT),
    /jwks
    (public key for verification)
OpenAPI Plugin (2 endpoints) - Docs:
  • /reference
    (interactive API docs with Scalar UI)
  • /generate-openapi-schema
    (get OpenAPI spec as JSON)

Passkey插件(5个端点)- 文档
  • /passkey/add
    ,
    /sign-in/passkey
    ,
    /passkey/list
    ,
    /passkey/delete
    ,
    /passkey/update
魔法链接插件(2个端点)- 文档
  • /sign-in/magic-link
    ,
    /magic-link/verify
用户名插件(2个端点)- 文档
  • /sign-in/username
    ,
    /username/is-available
手机号插件(5个端点)- 文档
  • /sign-in/phone-number
    ,
    /phone-number/send-otp
    ,
    /phone-number/verify
    ,
    /phone-number/request-password-reset
    ,
    /phone-number/reset-password
邮箱OTP插件(6个端点)- 文档
  • /email-otp/send-verification-otp
    ,
    /email-otp/check-verification-otp
    ,
    /sign-in/email-otp
    ,
    /email-otp/verify-email
    ,
    /forget-password/email-otp
    ,
    /email-otp/reset-password
匿名登录插件(1个端点)- 文档
  • /sign-in/anonymous
JWT插件(2个端点)- 文档
  • /token
    (获取JWT),
    /jwks
    (用于验证的公钥)
OpenAPI插件(2个端点)- 文档
  • /reference
    (带Scalar UI的交互式API文档)
  • /generate-openapi-schema
    (获取JSON格式的OpenAPI规范)

Server-Side API Methods (
auth.api.*
)

服务器端API方法(
auth.api.*

Every HTTP endpoint has a corresponding server-side method. Use these for:
  • Server-side middleware (protecting routes)
  • Background jobs (user cleanup, notifications)
  • Admin operations (bulk user management)
  • Custom auth flows (programmatic session creation)
每个HTTP端点都有对应的服务器端方法。适用于:
  • 服务器端中间件(保护路由)
  • 后台任务(用户清理、通知)
  • 管理员操作(批量用户管理)
  • 自定义认证流程(程序化创建会话)

Core API Methods

核心API方法

typescript
// Authentication
await auth.api.signUpEmail({
  body: { email, password, name },
  headers: request.headers,
});

await auth.api.signInEmail({
  body: { email, password, rememberMe: true },
  headers: request.headers,
});

await auth.api.signOut({ headers: request.headers });

// Session Management
const session = await auth.api.getSession({ headers: request.headers });

await auth.api.listSessions({ headers: request.headers });

await auth.api.revokeSession({
  body: { token: "session_token_here" },
  headers: request.headers,
});

// User Management
await auth.api.updateUser({
  body: { name: "New Name", image: "https://..." },
  headers: request.headers,
});

await auth.api.changeEmail({
  body: { newEmail: "newemail@example.com" },
  headers: request.headers,
});

await auth.api.deleteUser({
  body: { password: "current_password" },
  headers: request.headers,
});

// Account Linking
await auth.api.linkSocialAccount({
  body: { provider: "google" },
  headers: request.headers,
});

await auth.api.unlinkAccount({
  body: { providerId: "google", accountId: "google_123" },
  headers: request.headers,
});
typescript
// 认证
await auth.api.signUpEmail({
  body: { email, password, name },
  headers: request.headers,
});

await auth.api.signInEmail({
  body: { email, password, rememberMe: true },
  headers: request.headers,
});

await auth.api.signOut({ headers: request.headers });

// 会话管理
const session = await auth.api.getSession({ headers: request.headers });

await auth.api.listSessions({ headers: request.headers });

await auth.api.revokeSession({
  body: { token: "session_token_here" },
  headers: request.headers,
});

// 用户管理
await auth.api.updateUser({
  body: { name: "New Name", image: "https://..." },
  headers: request.headers,
});

await auth.api.changeEmail({
  body: { newEmail: "newemail@example.com" },
  headers: request.headers,
});

await auth.api.deleteUser({
  body: { password: "current_password" },
  headers: request.headers,
});

// 账户关联
await auth.api.linkSocialAccount({
  body: { provider: "google" },
  headers: request.headers,
});

await auth.api.unlinkAccount({
  body: { providerId: "google", accountId: "google_123" },
  headers: request.headers,
});

Plugin API Methods

插件API方法

2FA Plugin:
typescript
// Enable 2FA
const { totpUri, backupCodes } = await auth.api.enableTwoFactor({
  body: { issuer: "MyApp" },
  headers: request.headers,
});

// Verify TOTP code
await auth.api.verifyTOTP({
  body: { code: "123456", trustDevice: true },
  headers: request.headers,
});

// Generate backup codes
const { backupCodes } = await auth.api.generateBackupCodes({
  headers: request.headers,
});
Organization Plugin:
typescript
// Create organization
const org = await auth.api.createOrganization({
  body: { name: "Acme Corp", slug: "acme" },
  headers: request.headers,
});

// Add member
await auth.api.addMember({
  body: {
    userId: "user_123",
    role: "admin",
    organizationId: org.id,
  },
  headers: request.headers,
});

// Check permissions
const hasPermission = await auth.api.hasPermission({
  body: {
    organizationId: org.id,
    permission: "users:delete",
  },
  headers: request.headers,
});
Admin Plugin:
typescript
// List users with pagination
const users = await auth.api.listUsers({
  query: {
    search: "john",
    limit: 10,
    offset: 0,
    sortBy: "createdAt",
    sortOrder: "desc",
  },
  headers: request.headers,
});

// Ban user
await auth.api.banUser({
  body: {
    userId: "user_123",
    reason: "Violation of ToS",
    expiresAt: new Date("2025-12-31"),
  },
  headers: request.headers,
});

// Impersonate user (for admin support)
const impersonationSession = await auth.api.impersonateUser({
  body: {
    userId: "user_123",
    expiresIn: 3600, // 1 hour
  },
  headers: request.headers,
});

2FA插件
typescript
// 启用2FA
const { totpUri, backupCodes } = await auth.api.enableTwoFactor({
  body: { issuer: "MyApp" },
  headers: request.headers,
});

// 验证TOTP码
await auth.api.verifyTOTP({
  body: { code: "123456", trustDevice: true },
  headers: request.headers,
});

// 生成恢复码
const { backupCodes } = await auth.api.generateBackupCodes({
  headers: request.headers,
});
组织插件
typescript
// 创建组织
const org = await auth.api.createOrganization({
  body: { name: "Acme Corp", slug: "acme" },
  headers: request.headers,
});

// 添加成员
await auth.api.addMember({
  body: {
    userId: "user_123",
    role: "admin",
    organizationId: org.id,
  },
  headers: request.headers,
});

// 检查权限
const hasPermission = await auth.api.hasPermission({
  body: {
    organizationId: org.id,
    permission: "users:delete",
  },
  headers: request.headers,
});
管理员插件
typescript
// 分页列出用户
const users = await auth.api.listUsers({
  query: {
    search: "john",
    limit: 10,
    offset: 0,
    sortBy: "createdAt",
    sortOrder: "desc",
  },
  headers: request.headers,
});

// 封禁用户
await auth.api.banUser({
  body: {
    userId: "user_123",
    reason: "违反服务条款",
    expiresAt: new Date("2025-12-31"),
  },
  headers: request.headers,
});

// 模拟用户(用于管理员支持)
const impersonationSession = await auth.api.impersonateUser({
  body: {
    userId: "user_123",
    expiresIn: 3600, // 1小时
  },
  headers: request.headers,
});

When to Use Which

场景选择

Use CaseUse HTTP EndpointsUse
auth.api.*
Methods
Client-side auth✅ Yes❌ No
Server middleware❌ No✅ Yes
Background jobs❌ No✅ Yes
Admin dashboards✅ Yes (from client)✅ Yes (from server)
Custom auth flows❌ No✅ Yes
Mobile apps✅ Yes❌ No
API routes✅ Yes (proxy to handler)✅ Yes (direct calls)
Example: Protected Route Middleware
typescript
import { Hono } from "hono";
import { createAuth } from "./auth";
import { createDatabase } from "./db";

const app = new Hono<{ Bindings: Env }>();

// Middleware using server-side API
app.use("/api/protected/*", async (c, next) => {
  const db = createDatabase(c.env.DB);
  const auth = createAuth(db, c.env);

  // Use server-side method
  const session = await auth.api.getSession({
    headers: c.req.raw.headers,
  });

  if (!session) {
    return c.json({ error: "Unauthorized" }, 401);
  }

  // Attach to context
  c.set("user", session.user);
  c.set("session", session.session);

  await next();
});

// Protected route
app.get("/api/protected/profile", async (c) => {
  const user = c.get("user");
  return c.json({ user });
});

使用场景使用HTTP端点使用
auth.api.*
方法
客户端认证✅ 是❌ 否
服务器中间件❌ 否✅ 是
后台任务❌ 否✅ 是
管理员面板✅ 是(客户端调用)✅ 是(服务器端调用)
自定义认证流程❌ 否✅ 是
移动应用✅ 是❌ 否
API路由✅ 是(代理到处理器)✅ 是(直接调用)
示例:受保护路由中间件
typescript
import { Hono } from "hono";
import { createAuth } from "./auth";
import { createDatabase } from "./db";

const app = new Hono<{ Bindings: Env }>();

// 使用服务器端API的中间件
app.use("/api/protected/*", async (c, next) => {
  const db = createDatabase(c.env.DB);
  const auth = createAuth(db, c.env);

  // 使用服务器端方法
  const session = await auth.api.getSession({
    headers: c.req.raw.headers,
  });

  if (!session) {
    return c.json({ error: "未授权" }, 401);
  }

  // 附加到上下文
  c.set("user", session.user);
  c.set("session", session.session);

  await next();
});

// 受保护路由
app.get("/api/protected/profile", async (c) => {
  const user = c.get("user");
  return c.json({ user });
});

Discovering Available Endpoints

发现可用端点

Use the OpenAPI plugin to see all endpoints in your configuration:
typescript
import { betterAuth } from "better-auth";
import { openAPI } from "better-auth/plugins";

export const auth = betterAuth({
  database: /* ... */,
  plugins: [
    openAPI(), // Adds /api/auth/reference endpoint
  ],
});
Interactive documentation: Visit
http://localhost:8787/api/auth/reference
This shows a Scalar UI with:
  • ✅ All available endpoints grouped by feature
  • ✅ Request/response schemas with types
  • ✅ Try-it-out functionality (test endpoints in browser)
  • ✅ Authentication requirements
  • ✅ Code examples in multiple languages
Programmatic access:
typescript
const schema = await auth.api.generateOpenAPISchema();
console.log(JSON.stringify(schema, null, 2));
// Returns full OpenAPI 3.0 spec

使用OpenAPI插件查看配置中的所有端点:
typescript
import { betterAuth } from "better-auth";
import { openAPI } from "better-auth/plugins";

export const auth = betterAuth({
  database: /* ... */,
  plugins: [
    openAPI(), // 添加/api/auth/reference端点
  ],
});
交互式文档:访问
http://localhost:8787/api/auth/reference
该页面展示Scalar UI,包含:
  • ✅ 按功能分组的所有可用端点
  • ✅ 带类型的请求/响应schema
  • ✅ 在线测试功能(在浏览器中测试端点)
  • ✅ 认证要求
  • ✅ 多语言代码示例
程序化访问
typescript
const schema = await auth.api.generateOpenAPISchema();
console.log(JSON.stringify(schema, null, 2));
// 返回完整的OpenAPI 3.0规范

Quantified Time Savings

量化时间节省

Building from scratch (manual implementation):
  • Core auth endpoints (sign-up, sign-in, OAuth, sessions): 40 hours
  • Email verification & password reset: 10 hours
  • 2FA system (TOTP, backup codes, email OTP): 20 hours
  • Organizations (teams, invitations, RBAC): 60 hours
  • Admin panel (user management, impersonation): 30 hours
  • Testing & debugging: 50 hours
  • Security hardening: 20 hours
Total manual effort: ~220 hours (5.5 weeks full-time)
With better-auth:
  • Initial setup: 2-4 hours
  • Customization & styling: 2-4 hours
Total with better-auth: 4-8 hours
Savings: ~97% development time

从零构建(手动实现):
  • 核心认证端点(注册、登录、OAuth、会话):40小时
  • 邮箱验证与密码重置:10小时
  • 2FA系统(TOTP、恢复码、邮箱OTP):20小时
  • 组织管理(团队、邀请、RBAC):60小时
  • 管理员面板(用户管理、模拟登录):30小时
  • 测试与调试:50小时
  • 安全加固:20小时
手动实现总耗时约220小时(全职5.5周)
使用better-auth
  • 初始配置:2-4小时
  • 定制与样式:2-4小时
使用better-auth总耗时4-8小时
时间节省约97%的开发时间

Key Takeaway

核心优势

better-auth provides 80+ production-ready endpoints covering:
  • ✅ Core authentication (20 endpoints)
  • ✅ 2FA & passwordless (15 endpoints)
  • ✅ Organizations & teams (35 endpoints)
  • ✅ Admin & user management (15 endpoints)
  • ✅ Social OAuth (auto-configured callbacks)
  • ✅ OpenAPI documentation (interactive UI)
You write zero endpoint code. Just configure features and call
auth.handler()
.

better-auth提供80+个生产就绪的端点,涵盖:
  • ✅ 核心认证(20个端点)
  • ✅ 2FA与无密码登录(15个端点)
  • ✅ 组织与团队管理(35个端点)
  • ✅ 管理员与用户管理(15个端点)
  • ✅ 社交OAuth(自动配置回调)
  • ✅ OpenAPI文档(交互式UI)
你无需编写任何端点代码,只需配置功能并调用
auth.handler()
即可。

Known Issues & Solutions

已知问题与解决方案

Issue 1: "d1Adapter is not exported" Error

问题1:“d1Adapter is not exported”错误

Problem: Code shows
import { d1Adapter } from 'better-auth/adapters/d1'
but this doesn't exist.
Symptoms: TypeScript error or runtime error about missing export.
Solution: Use Drizzle or Kysely instead:
typescript
// ❌ WRONG - This doesn't exist
import { d1Adapter } from 'better-auth/adapters/d1'
database: d1Adapter(env.DB)

// ✅ CORRECT - Use Drizzle
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
import { drizzle } from 'drizzle-orm/d1'
const db = drizzle(env.DB, { schema })
database: drizzleAdapter(db, { provider: "sqlite" })

// ✅ CORRECT - Use Kysely
import { Kysely } from 'kysely'
import { D1Dialect } from 'kysely-d1'
database: {
  db: new Kysely({ dialect: new D1Dialect({ database: env.DB }) }),
  type: "sqlite"
}
Source: Verified from 4 production repositories using better-auth + D1

问题:代码中使用
import { d1Adapter } from 'better-auth/adapters/d1'
但该方法不存在。
症状:TypeScript错误或运行时错误提示缺少导出项。
解决方案:使用Drizzle或Kysely替代:
typescript
// ❌ 错误 - 该方法不存在
import { d1Adapter } from 'better-auth/adapters/d1'
database: d1Adapter(env.DB)

// ✅ 正确 - 使用Drizzle
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
import { drizzle } from 'drizzle-orm/d1'
const db = drizzle(env.DB, { schema })
database: drizzleAdapter(db, { provider: "sqlite" })

// ✅ 正确 - 使用Kysely
import { Kysely } from 'kysely'
import { D1Dialect } from 'kysely-d1'
database: {
  db: new Kysely({ dialect: new D1Dialect({ database: env.DB }) }),
  type: "sqlite"
}
来源:已在4个使用better-auth + D1的生产仓库中验证

Issue 2: Schema Generation Fails

问题2:Schema生成失败

Problem:
npx better-auth migrate
doesn't create D1-compatible schema.
Symptoms: Migration SQL has wrong syntax or doesn't work with D1.
Solution: Use Drizzle Kit to generate migrations:
bash
undefined
问题
npx better-auth migrate
无法创建兼容D1的schema。
症状:迁移SQL语法错误或无法在D1中运行。
解决方案:使用Drizzle Kit生成迁移文件:
bash
// 从Drizzle schema生成迁移文件
npx drizzle-kit generate

// 应用到D1
wrangler d1 migrations apply my-app-db --remote
原因:Drizzle Kit生成的SQL兼容SQLite,可在D1中运行。

Generate migration from Drizzle schema

问题3:“CamelCase”与“snake_case”列名不匹配

npx drizzle-kit generate
问题:数据库中是
email_verified
但better-auth期望
emailVerified
症状:会话读取失败,用户数据字段缺失。
解决方案:在Kysely中使用
CamelCasePlugin
或正确配置Drizzle:
使用Kysely
typescript
import { CamelCasePlugin } from "kysely";

new Kysely({
  dialect: new D1Dialect({ database: env.DB }),
  plugins: [new CamelCasePlugin()], // 在两种命名规范之间自动转换
})
使用Drizzle:从一开始就使用camelCase定义schema(如示例所示)。

Apply to D1

问题4:D1最终一致性问题

wrangler d1 migrations apply my-app-db --remote

**Why**: Drizzle Kit generates SQLite-compatible SQL that works with D1.

---
问题:写入后立即读取会话返回旧数据。
症状:用户登录后,下一次请求中
getSession()
返回null。
解决方案:使用Cloudflare KV存储会话(强一致性):
typescript
import { betterAuth } from "better-auth";

export function createAuth(db: Database, env: Env) {
  return betterAuth({
    database: drizzleAdapter(db, { provider: "sqlite" }),
    session: {
      storage: {
        get: async (sessionId) => {
          const session = await env.SESSIONS_KV.get(sessionId);
          return session ? JSON.parse(session) : null;
        },
        set: async (sessionId, session, ttl) => {
          await env.SESSIONS_KV.put(sessionId, JSON.stringify(session), {
            expirationTtl: ttl,
          });
        },
        delete: async (sessionId) => {
          await env.SESSIONS_KV.delete(sessionId);
        },
      },
    },
  });
}
添加到
wrangler.toml
toml
[[kv_namespaces]]
binding = "SESSIONS_KV"
id = "your-kv-namespace-id"

Issue 3: "CamelCase" vs "snake_case" Column Mismatch

问题5:SPA应用的CORS错误

Problem: Database has
email_verified
but better-auth expects
emailVerified
.
Symptoms: Session reads fail, user data missing fields.
Solution: Use
CamelCasePlugin
with Kysely or configure Drizzle properly:
With Kysely:
typescript
import { CamelCasePlugin } from "kysely";

new Kysely({
  dialect: new D1Dialect({ database: env.DB }),
  plugins: [new CamelCasePlugin()], // Converts between naming conventions
})
With Drizzle: Define schema with camelCase from the start (as shown in examples).

问题:认证API与前端不在同一域名下时出现CORS错误。
症状:浏览器控制台中出现
Access-Control-Allow-Origin
错误。
解决方案:在Worker中配置CORS头:
typescript
import { cors } from "hono/cors";

app.use(
  "/api/auth/*",
  cors({
    origin: ["https://yourdomain.com", "http://localhost:3000"],
    credentials: true, // 允许Cookie
    allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
  })
);

Issue 4: D1 Eventual Consistency

问题6:OAuth重定向URI不匹配

Problem: Session reads immediately after write return stale data.
Symptoms: User logs in but
getSession()
returns null on next request.
Solution: Use Cloudflare KV for session storage (strong consistency):
typescript
import { betterAuth } from "better-auth";

export function createAuth(db: Database, env: Env) {
  return betterAuth({
    database: drizzleAdapter(db, { provider: "sqlite" }),
    session: {
      storage: {
        get: async (sessionId) => {
          const session = await env.SESSIONS_KV.get(sessionId);
          return session ? JSON.parse(session) : null;
        },
        set: async (sessionId, session, ttl) => {
          await env.SESSIONS_KV.put(sessionId, JSON.stringify(session), {
            expirationTtl: ttl,
          });
        },
        delete: async (sessionId) => {
          await env.SESSIONS_KV.delete(sessionId);
        },
      },
    },
  });
}
Add to
wrangler.toml
:
toml
[[kv_namespaces]]
binding = "SESSIONS_KV"
id = "your-kv-namespace-id"

问题:社交登录失败,提示“redirect_uri_mismatch”错误。
症状:Google/GitHub OAuth在用户授权后返回错误。
解决方案:确保OAuth提供者设置中的URI与实际URI完全一致:
提供者设置:https://yourdomain.com/api/auth/callback/google
better-auth URL:  https://yourdomain.com/api/auth/callback/google

❌ 错误:http与https不匹配、末尾有斜杠、子域名不匹配
✅ 正确:字符完全匹配
检查better-auth回调URL
typescript
// 格式始终为:{baseURL}/api/auth/callback/{provider}
const callbackURL = `${env.BETTER_AUTH_URL}/api/auth/callback/google`;
console.log("在Google控制台中配置此URL:", callbackURL);

Issue 5: CORS Errors for SPA Applications

问题7:依赖缺失

Problem: CORS errors when auth API is on different origin than frontend.
Symptoms:
Access-Control-Allow-Origin
errors in browser console.
Solution: Configure CORS headers in Worker:
typescript
import { cors } from "hono/cors";

app.use(
  "/api/auth/*",
  cors({
    origin: ["https://yourdomain.com", "http://localhost:3000"],
    credentials: true, // Allow cookies
    allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
  })
);

问题:TypeScript错误或运行时错误提示缺少包。
症状
Cannot find module 'drizzle-orm'
或类似错误。
解决方案:安装所有必需的包:
Drizzle方案
bash
npm install better-auth drizzle-orm drizzle-kit @cloudflare/workers-types
Kysely方案
bash
npm install better-auth kysely kysely-d1 @cloudflare/workers-types

Issue 6: OAuth Redirect URI Mismatch

问题8:邮箱验证邮件未发送

Problem: Social sign-in fails with "redirect_uri_mismatch" error.
Symptoms: Google/GitHub OAuth returns error after user consent.
Solution: Ensure exact match in OAuth provider settings:
Provider setting: https://yourdomain.com/api/auth/callback/google
better-auth URL:  https://yourdomain.com/api/auth/callback/google

❌ Wrong: http vs https, trailing slash, subdomain mismatch
✅ Right: Exact character-for-character match
Check better-auth callback URL:
typescript
// It's always: {baseURL}/api/auth/callback/{provider}
const callbackURL = `${env.BETTER_AUTH_URL}/api/auth/callback/google`;
console.log("Configure this URL in Google Console:", callbackURL);

问题:邮箱验证链接从未送达。
症状:用户注册后未收到邮件。
解决方案:实现
sendVerificationEmail
处理器:
typescript
export const auth = betterAuth({
  database: /* ... */,
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
  },
  emailVerification: {
    sendVerificationEmail: async ({ user, url }) => {
      // 使用你的邮件服务(SendGrid、Resend等)
      await sendEmail({
        to: user.email,
        subject: "验证你的邮箱",
        html: `
          <p>点击下方链接验证你的邮箱:</p>
          <a href="${url}">验证邮箱</a>
        `,
      });
    },
    sendOnSignUp: true,
    autoSignInAfterVerification: true,
    expiresIn: 3600, // 1小时
  },
});
Cloudflare环境:使用Cloudflare Email Routing或外部服务(Resend、SendGrid)。

Issue 7: Missing Dependencies

问题9:会话过期过快

Problem: TypeScript errors or runtime errors about missing packages.
Symptoms:
Cannot find module 'drizzle-orm'
or similar.
Solution: Install all required packages:
For Drizzle approach:
bash
npm install better-auth drizzle-orm drizzle-kit @cloudflare/workers-types
For Kysely approach:
bash
npm install better-auth kysely kysely-d1 @cloudflare/workers-types

问题:会话意外过期或永不过期。
症状:用户意外登出或登出后会话仍存在。
解决方案:配置会话过期时间:
typescript
export const auth = betterAuth({
  database: /* ... */,
  session: {
    expiresIn: 60 * 60 * 24 * 7, // 7天(秒为单位)
    updateAge: 60 * 60 * 24, // 每24小时更新一次会话
  },
});

Issue 8: Email Verification Not Sending

问题10:社交提供者用户数据缺失

Problem: Email verification links never arrive.
Symptoms: User signs up, but no email received.
Solution: Implement
sendVerificationEmail
handler:
typescript
export const auth = betterAuth({
  database: /* ... */,
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
  },
  emailVerification: {
    sendVerificationEmail: async ({ user, url }) => {
      // Use your email service (SendGrid, Resend, etc.)
      await sendEmail({
        to: user.email,
        subject: "Verify your email",
        html: `
          <p>Click the link below to verify your email:</p>
          <a href="${url}">Verify Email</a>
        `,
      });
    },
    sendOnSignUp: true,
    autoSignInAfterVerification: true,
    expiresIn: 3600, // 1 hour
  },
});
For Cloudflare: Use Cloudflare Email Routing or external service (Resend, SendGrid).

问题:社交登录成功但用户数据(姓名、头像)缺失。
症状
session.user.name
在Google/GitHub登录后为null。
解决方案:请求额外的权限范围:
typescript
socialProviders: {
  google: {
    clientId: env.GOOGLE_CLIENT_ID,
    clientSecret: env.GOOGLE_CLIENT_SECRET,
    scope: ["openid", "email", "profile"], // 包含'profile'以获取姓名/头像
  },
  github: {
    clientId: env.GITHUB_CLIENT_ID,
    clientSecret: env.GITHUB_CLIENT_SECRET,
    scope: ["user:email", "read:user"], // 'read:user'以获取完整资料
  },
}

Issue 9: Session Expires Too Quickly

问题11:Drizzle Schema的TypeScript错误

Problem: Session expires unexpectedly or never expires.
Symptoms: User logged out unexpectedly or session persists after logout.
Solution: Configure session expiration:
typescript
export const auth = betterAuth({
  database: /* ... */,
  session: {
    expiresIn: 60 * 60 * 24 * 7, // 7 days (in seconds)
    updateAge: 60 * 60 * 24, // Update session every 24 hours
  },
});

问题:TypeScript提示schema类型错误。
症状
Type 'DrizzleD1Database' is not assignable to...
解决方案:从数据库中导出正确的类型:
typescript
// src/db/index.ts
import { drizzle, type DrizzleD1Database } from "drizzle-orm/d1";
import * as schema from "./schema";

export type Database = DrizzleD1Database<typeof schema>;

export function createDatabase(d1: D1Database): Database {
  return drizzle(d1, { schema });
}

Issue 10: Social Provider Missing User Data

问题12:Wrangler开发模式无法运行

Problem: Social sign-in succeeds but missing user data (name, avatar).
Symptoms:
session.user.name
is null after Google/GitHub sign-in.
Solution: Request additional scopes:
typescript
socialProviders: {
  google: {
    clientId: env.GOOGLE_CLIENT_ID,
    clientSecret: env.GOOGLE_CLIENT_SECRET,
    scope: ["openid", "email", "profile"], // Include 'profile' for name/image
  },
  github: {
    clientId: env.GITHUB_CLIENT_ID,
    clientSecret: env.GITHUB_CLIENT_SECRET,
    scope: ["user:email", "read:user"], // 'read:user' for full profile
  },
}

问题
wrangler dev
因数据库错误失败。
症状:本地开发中出现“数据库未找到”或迁移错误。
解决方案:先在本地应用迁移:
bash
// 应用迁移到本地D1
wrangler d1 migrations apply my-app-db --local

// 然后启动开发服务器
wrangler dev

Issue 11: TypeScript Errors with Drizzle Schema

问题13:用户数据更新后UI未同步(搭配TanStack Query)

Problem: TypeScript complains about schema types.
Symptoms:
Type 'DrizzleD1Database' is not assignable to...
Solution: Export proper types from database:
typescript
// src/db/index.ts
import { drizzle, type DrizzleD1Database } from "drizzle-orm/d1";
import * as schema from "./schema";

export type Database = DrizzleD1Database<typeof schema>;

export function createDatabase(d1: D1Database): Database {
  return drizzle(d1, { schema });
}

问题:更新用户数据(如头像、姓名)后,尽管调用了
queryClient.invalidateQueries()
useSession()
仍未显示变更。
症状:头像或用户资料数据在更新成功后仍显示旧值。TanStack Query缓存显示更新后的数据,但better-auth会话仍显示旧值。
根本原因:better-auth使用nanostores管理会话状态,而非TanStack Query。调用
queryClient.invalidateQueries()
仅会使React Query缓存失效,不会影响better-auth的nanostore。
解决方案:更新用户数据后手动通知nanostore:
typescript
// 更新用户数据
const { data, error } = await authClient.updateUser({
  image: newAvatarUrl,
  name: newName
})

if (!error) {
  // 手动使better-auth会话状态失效
  authClient.$store.notify('$sessionSignal')

  // 可选:如果其他数据使用了React Query,也使其失效
  queryClient.invalidateQueries({ queryKey: ['user-profile'] })
}
适用场景
  • 同时使用better-auth和TanStack Query
  • 更新用户资料字段(姓名、头像、邮箱)
  • 任何在客户端修改会话用户数据的操作
替代方案:调用
useSession()
refetch()
方法,但
$store.notify()
更直接:
typescript
const { data: session, refetch } = authClient.useSession()
// 更新后
await refetch()
注意
$store
是未公开的内部API。此模式已在生产环境中验证,但未来better-auth版本可能会有变化。
来源:社区发现的解决方案,已在生产环境中使用

Issue 12: Wrangler Dev Mode Not Working

迁移指南

从Clerk迁移

Problem:
wrangler dev
fails with database errors.
Symptoms: "Database not found" or migration errors in local dev.
Solution: Apply migrations locally first:
bash
undefined
主要差异
  • Clerk:第三方服务 → better-auth:自托管
  • Clerk:专有 → better-auth:开源
  • Clerk:按月付费 → better-auth:免费
迁移步骤
  1. 导出用户数据:从Clerk导出(CSV或API)
  2. 导入到better-auth数据库
    typescript
    // 迁移脚本
    const clerkUsers = await fetchClerkUsers();
    
    for (const clerkUser of clerkUsers) {
      await db.insert(user).values({
        id: clerkUser.id,
        email: clerkUser.email,
        emailVerified: clerkUser.email_verified,
        name: clerkUser.first_name + " " + clerkUser.last_name,
        image: clerkUser.profile_image_url,
      });
    }
  3. 替换Clerk SDK为better-auth客户端:
    typescript
    // 之前(Clerk)
    import { useUser } from "@clerk/nextjs";
    const { user } = useUser();
    
    // 之后(better-auth)
    import { authClient } from "@/lib/auth-client";
    const { data: session } = authClient.useSession();
    const user = session?.user;
  4. 更新中间件用于会话验证
  5. 配置社交提供者(使用相同的OAuth应用,不同的配置)

Apply migrations to local D1

从Auth.js(NextAuth)迁移

wrangler d1 migrations apply my-app-db --local
主要差异
  • Auth.js:功能有限 → better-auth:功能全面(2FA、组织管理等)
  • Auth.js:依赖回调 → better-auth:基于插件
  • Auth.js:会话处理不一致 → better-auth:会话处理一致
迁移步骤
  1. 数据库schema:Auth.js与better-auth的schema类似,但列名不同
  2. 替换配置
    typescript
    // 之前(Auth.js)
    import NextAuth from "next-auth";
    import GoogleProvider from "next-auth/providers/google";
    
    export default NextAuth({
      providers: [GoogleProvider({ /* ... */ })],
    });
    
    // 之后(better-auth)
    import { betterAuth } from "better-auth";
    
    export const auth = betterAuth({
      socialProviders: {
        google: { /* ... */ },
      },
    });
  3. 更新客户端钩子
    typescript
    // 之前
    import { useSession } from "next-auth/react";
    
    // 之后
    import { authClient } from "@/lib/auth-client";
    const { data: session } = authClient.useSession();

Then run dev server

额外资源

官方文档

Issue 13: User Data Updates Not Reflecting in UI (with TanStack Query)

核心概念

Problem: After updating user data (e.g., avatar, name), changes don't appear in
useSession()
despite calling
queryClient.invalidateQueries()
.
Symptoms: Avatar image or user profile data appears stale after successful update. TanStack Query cache shows updated data, but better-auth session still shows old values.
Root Cause: better-auth uses nanostores for session state management, not TanStack Query. Calling
queryClient.invalidateQueries()
only invalidates React Query cache, not the better-auth nanostore.
Solution: Manually notify the nanostore after updating user data:
typescript
// Update user data
const { data, error } = await authClient.updateUser({
  image: newAvatarUrl,
  name: newName
})

if (!error) {
  // Manually invalidate better-auth session state
  authClient.$store.notify('$sessionSignal')

  // Optional: Also invalidate React Query if using it for other data
  queryClient.invalidateQueries({ queryKey: ['user-profile'] })
}
When to use:
  • Using better-auth + TanStack Query together
  • Updating user profile fields (name, image, email)
  • Any operation that modifies session user data client-side
Alternative: Call
refetch()
from
useSession()
, but
$store.notify()
is more direct:
typescript
const { data: session, refetch } = authClient.useSession()
// After update
await refetch()
Note:
$store
is an undocumented internal API. This pattern is production-validated but may change in future better-auth versions.
Source: Community-discovered pattern, production use verified

Migration Guides

认证方式

From Clerk

Key differences:
  • Clerk: Third-party service → better-auth: Self-hosted
  • Clerk: Proprietary → better-auth: Open source
  • Clerk: Monthly cost → better-auth: Free
Migration steps:
  1. Export user data from Clerk (CSV or API)
  2. Import into better-auth database:
    typescript
    // migration script
    const clerkUsers = await fetchClerkUsers();
    
    for (const clerkUser of clerkUsers) {
      await db.insert(user).values({
        id: clerkUser.id,
        email: clerkUser.email,
        emailVerified: clerkUser.email_verified,
        name: clerkUser.first_name + " " + clerkUser.last_name,
        image: clerkUser.profile_image_url,
      });
    }
  3. Replace Clerk SDK with better-auth client:
    typescript
    // Before (Clerk)
    import { useUser } from "@clerk/nextjs";
    const { user } = useUser();
    
    // After (better-auth)
    import { authClient } from "@/lib/auth-client";
    const { data: session } = authClient.useSession();
    const user = session?.user;
  4. Update middleware for session verification
  5. Configure social providers (same OAuth apps, different config)

From Auth.js (NextAuth)

插件文档

Key differences:
  • Auth.js: Limited features → better-auth: Comprehensive (2FA, orgs, etc.)
  • Auth.js: Callbacks-heavy → better-auth: Plugin-based
  • Auth.js: Session handling varies → better-auth: Consistent
Migration steps:
  1. Database schema: Auth.js and better-auth use similar schemas, but column names differ
  2. Replace configuration:
    typescript
    // Before (Auth.js)
    import NextAuth from "next-auth";
    import GoogleProvider from "next-auth/providers/google";
    
    export default NextAuth({
      providers: [GoogleProvider({ /* ... */ })],
    });
    
    // After (better-auth)
    import { betterAuth } from "better-auth";
    
    export const auth = betterAuth({
      socialProviders: {
        google: { /* ... */ },
      },
    });
  3. Update client hooks:
    typescript
    // Before
    import { useSession } from "next-auth/react";
    
    // After
    import { authClient } from "@/lib/auth-client";
    const { data: session } = authClient.useSession();

Additional Resources

框架集成

Official Documentation

Core Concepts

社区与支持

Authentication Methods

相关文档

Plugin Documentation

生产环境示例

已验证的D1仓库(均使用Drizzle或Kysely):
  1. zpg6/better-auth-cloudflare - Drizzle + D1(包含CLI)
  2. zwily/example-react-router-cloudflare-d1-drizzle-better-auth - Drizzle + D1
  3. foxlau/react-router-v7-better-auth - Drizzle + D1
  4. matthewlynch/better-auth-react-router-cloudflare-d1 - Kysely + D1
没有仓库使用直接的
d1Adapter
——全部需要Drizzle/Kysely。

Framework Integrations

版本兼容性

已测试版本
  • better-auth@1.3.34
  • drizzle-orm@0.44.7
  • drizzle-kit@0.31.6
  • kysely@0.28.8
  • kysely-d1@0.4.0
  • @cloudflare/workers-types@latest
  • hono@4.0.0
  • Node.js 18+, Bun 1.0+
破坏性变更:升级时查看更新日志:https://github.com/better-auth/better-auth/releases

令牌效率
  • 未使用本指南:约28,000令牌(D1适配器错误、TanStack Start Cookie问题、nanostore失效、OAuth流程、API发现)
  • 使用本指南:约5,600令牌(聚焦错误+破坏性变更+API参考)
  • 节省:约80%(约22,400令牌)
避免的错误:13个有明确解决方案的已记录问题 核心价值:D1适配器要求、v1.4.0/v1.3破坏性变更、TanStack Start修复、nanostore模式、80+端点参考

最后验证时间:2025-11-22 | 指南版本:3.0.0 | 变更:新增v1.4.0(仅ESM、无状态会话、SCIM)和v1.3(SSO/SAML、多团队)相关内容。移除教程/设置部分(约700行)。聚焦错误预防+破坏性变更+API参考。

Community & Support

Related Documentation

Production Examples

Verified working D1 repositories (all use Drizzle or Kysely):
  1. zpg6/better-auth-cloudflare - Drizzle + D1 (includes CLI)
  2. zwily/example-react-router-cloudflare-d1-drizzle-better-auth - Drizzle + D1
  3. foxlau/react-router-v7-better-auth - Drizzle + D1
  4. matthewlynch/better-auth-react-router-cloudflare-d1 - Kysely + D1
None use a direct
d1Adapter
- all require Drizzle/Kysely.

Version Compatibility

Tested with:
  • better-auth@1.3.34
  • drizzle-orm@0.44.7
  • drizzle-kit@0.31.6
  • kysely@0.28.8
  • kysely-d1@0.4.0
  • @cloudflare/workers-types@latest
  • hono@4.0.0
  • Node.js 18+, Bun 1.0+
Breaking changes: Check changelog when upgrading: https://github.com/better-auth/better-auth/releases

Token Efficiency:
  • Without skill: ~28,000 tokens (D1 adapter errors, TanStack Start cookies, nanostore invalidation, OAuth flows, API discovery)
  • With skill: ~5,600 tokens (focused on errors + breaking changes + API reference)
  • Savings: ~80% (~22,400 tokens)
Errors prevented: 13 documented issues with exact solutions Key value: D1 adapter requirement, v1.4.0/v1.3 breaking changes, TanStack Start fix, nanostore pattern, 80+ endpoint reference

Last verified: 2025-11-22 | Skill version: 3.0.0 | Changes: Added v1.4.0 (ESM-only, stateless sessions, SCIM) and v1.3 (SSO/SAML, multi-team) knowledge gaps. Removed tutorial/setup (~700 lines). Focused on error prevention + breaking changes + API reference.