Skip to main content
This page documents every option accepted by betterAuth(). For the full TypeScript source, see packages/better-auth/src/types/options.ts.

Quick example

auth.ts
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  appName: "My App",
  baseURL: "https://example.com",
  secret: process.env.BETTER_AUTH_SECRET,
  database: { dialect: "postgres", type: "postgres" },
  emailAndPassword: { enabled: true },
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
  },
});

Top-level options

appName
string
The human-readable name of your application. Used in emails and the default error page.
appName: "My App"
baseURL
string
Root URL where your application server is hosted. If a path component is included it takes precedence over basePath.Falls back to the BETTER_AUTH_URL environment variable, then to request inference. Always set this explicitly in production.
baseURL: "https://example.com"
Relying on request inference is not recommended. For security and stability, always set baseURL explicitly or via the BETTER_AUTH_URL environment variable.
basePath
string
default:"/api/auth"
The path prefix where Better Auth routes are mounted. Overridden when baseURL includes a path.
basePath: "/api/auth"
secret
string
Secret used for encryption, signing, and hashing. In production Better Auth throws if this is not set.Reads from BETTER_AUTH_SECRET or AUTH_SECRET environment variables when not provided explicitly.
# generate a strong secret
openssl rand -base64 32
secret: process.env.BETTER_AUTH_SECRET
secrets
Array<{ version: number; value: string }>
Versioned secrets for non-destructive secret rotation. The first entry is the active key for all new encryption; remaining entries are decryption-only.
secrets: [
  { version: 2, value: "new-secret-key" },
  { version: 1, value: "old-secret-key" },
]
Can also be set via the BETTER_AUTH_SECRETS environment variable:
.env
BETTER_AUTH_SECRETS=2:new-secret-base64,1:old-secret-base64
When secrets is configured, the singular secret is only used as a fallback for decrypting legacy data. Both can coexist during migration.
trustedOrigins
string[] | ((request?: Request) => string[] | Promise<string[]>)
Origins allowed to make cross-origin requests. Accepts a static array, wildcard patterns, or an async function for dynamic resolution.Static:
trustedOrigins: ["http://localhost:3000", "https://example.com"]
Wildcard patterns:
trustedOrigins: [
  "https://*.example.com",      // all HTTPS subdomains
  "myapp://",                    // mobile deep-link scheme
  "chrome-extension://ABC123",  // browser extension
]
PatternDescription
?Matches exactly one character (not /)
*Matches zero or more characters that don’t cross /
**Matches zero or more characters including /
Dynamic:
trustedOrigins: async (request) => {
  if (!request) return ["https://my-frontend.com"];
  return ["https://dynamic-origin.com"];
}
The request parameter is undefined during initialization and when calling auth.api directly. Always return default origins for the undefined case.
plugins
BetterAuthPlugin[]
List of Better Auth plugins to load.
import { emailOTP } from "better-auth/plugins";

plugins: [
  emailOTP({
    sendVerificationOTP: async ({ email, otp, type }) => {
      // send OTP
    },
  }),
]
disabledPaths
string[]
Auth paths that should return 404. Useful for disabling sign-up in closed-beta or invite-only apps.
disabledPaths: ["/sign-up/email"]
telemetry
{ enabled: boolean }
default:"{ enabled: false }"
Controls anonymous usage telemetry sent to the Better Auth team.
telemetry: { enabled: false }

database

database
DatabaseConfiguration
Primary database configuration. Supports PostgreSQL, MySQL, and SQLite via the built-in Kysely adapter, or any ORM adapter (Prisma, Drizzle, MongoDB).
database: {
  dialect: "postgres",
  type: "postgres",
  casing: "camel",
}
Read the database docs for adapter-specific setup.
secondaryStorage
SecondaryStorageConfig
Optional fast key-value storage (Redis, Cloudflare KV, etc.) for sessions, verification tokens, and rate-limit counters.
secondaryStorage: {
  get: async (key) => redis.get(key),
  set: async (key, value, ttl) => redis.set(key, value, "EX", ttl),
  delete: async (key) => redis.del(key),
}

emailAndPassword

emailAndPassword.enabled
boolean
default:"false"
Enable email and password authentication.
emailAndPassword.disableSignUp
boolean
default:"false"
Prevent new accounts from being created via email/password.
emailAndPassword.requireEmailVerification
boolean
Block session creation until the user verifies their email.
emailAndPassword.minPasswordLength
number
default:"8"
Minimum accepted password length.
emailAndPassword.maxPasswordLength
number
default:"128"
Maximum accepted password length.
emailAndPassword.autoSignIn
boolean
default:"true"
Automatically create a session after a successful sign-up.
emailAndPassword.sendResetPassword
(opts: { user, url, token }) => Promise<void>
Function called to deliver the password-reset email.
sendResetPassword: async ({ user, url, token }) => {
  await mailer.send({ to: user.email, subject: "Reset password", html: url });
}
emailAndPassword.resetPasswordTokenExpiresIn
number
default:"3600"
Seconds until a reset-password token expires.
emailAndPassword.revokeSessionsOnPasswordReset
boolean
default:"false"
Revoke all other sessions when a user resets their password.
emailAndPassword.password
{ hash, verify }
Override the default scrypt password hashing with a custom implementation.
password: {
  hash: async (password) => bcrypt.hash(password, 10),
  verify: async ({ hash, password }) => bcrypt.compare(password, hash),
}

emailVerification

emailVerification.sendVerificationEmail
(opts: { user, url, token }) => Promise<void>
Function called to send verification emails.
sendVerificationEmail: async ({ user, url, token }) => {
  await mailer.send({ to: user.email, subject: "Verify email", html: url });
}
emailVerification.sendOnSignUp
boolean
Send a verification email automatically after sign-up. When undefined, follows the requireEmailVerification setting.
emailVerification.autoSignInAfterVerification
boolean
Automatically sign the user in after they verify their email.
emailVerification.expiresIn
number
default:"3600"
Seconds until a verification token expires.

socialProviders

Configure one or more OAuth / OIDC providers. Each key is a provider slug.
socialProviders.[provider].clientId
string
required
OAuth client ID issued by the provider.
socialProviders.[provider].clientSecret
string
required
OAuth client secret issued by the provider.
socialProviders.[provider].redirectURI
string
Custom callback URL. Defaults to {baseURL}/api/auth/callback/{provider}.
socialProviders.[provider].scope
string[]
Additional OAuth scopes to request beyond the provider defaults.
socialProviders.[provider].mapProfileToUser
(profile) => Partial<User>
Transform the raw provider profile into Better Auth user fields.
socialProviders.[provider].disableSignUp
boolean
Prevent new accounts from being created through this provider.
socialProviders: {
  google: {
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  },
  github: {
    clientId: process.env.GITHUB_CLIENT_ID!,
    clientSecret: process.env.GITHUB_CLIENT_SECRET!,
  },
}

session

session.modelName
string
default:"session"
Database table/model name for sessions.
session.expiresIn
number
default:"604800"
Session lifetime in seconds (default: 7 days).
session.updateAge
number
default:"86400"
Extend the session expiry when the session age exceeds this threshold (seconds). Set to 0 to refresh on every request.
session.disableSessionRefresh
boolean
default:"false"
Disable automatic expiry extension regardless of updateAge.
session.additionalFields
Record<string, FieldConfig>
Extra fields to store on the session record.
session.storeSessionInDatabase
boolean
default:"false"
Persist sessions in the primary database even when secondaryStorage is configured.
Cache session data in a short-lived signed cookie to avoid a database round-trip on every request.
cookieCache: {
  enabled: true,
  maxAge: 300, // 5 minutes
}
session: {
  expiresIn: 604800,   // 7 days
  updateAge: 86400,    // refresh once per day
  cookieCache: {
    enabled: true,
    maxAge: 300,
  },
}

user

user.modelName
string
default:"user"
Database table/model name for users.
user.fields
Record<string, string>
Map built-in field names to different database column names.
fields: { email: "emailAddress", name: "fullName" }
user.additionalFields
Record<string, FieldConfig>
Extra fields added to the user table. Set input: false for fields that should not be settable by the client (e.g. role).
additionalFields: {
  role: { type: "string", input: false },
}
user.changeEmail
ChangeEmailConfig
Configuration for the change-email flow.
  • enabled — allow authenticated users to change their email
  • sendChangeEmailConfirmation — function to deliver confirmation link
user.deleteUser
DeleteUserConfig
Configuration for account deletion.
  • enabled — allow users to delete their own account
  • sendDeleteAccountVerification — function to deliver confirmation link
  • beforeDelete / afterDelete — lifecycle callbacks

account

account.encryptOAuthTokens
boolean
default:"false"
Encrypt access/refresh tokens before writing them to the database.
account.accountLinking.enabled
boolean
default:"true"
Allow users to link multiple OAuth providers to one account.
account.accountLinking.trustedProviders
string[] | ((request?) => string[])
Providers whose verified email is trusted for automatic account linking.
trustedProviders: ["google", "github", "email-password"]

rateLimit

rateLimit.enabled
boolean
Defaults to true in production, false in development.
rateLimit.window
number
default:"10"
Time window in seconds.
rateLimit.max
number
default:"100"
Maximum requests per window across all routes.
rateLimit.customRules
Record<string, { window: number; max: number }>
Per-path overrides.
customRules: {
  "/sign-in/email": { window: 10, max: 5 },
}
rateLimit.storage
'memory' | 'database' | 'secondary-storage'
default:"memory"
Where to persist rate-limit counters.
rateLimit: {
  enabled: true,
  window: 10,
  max: 100,
  customRules: {
    "/sign-in/email": { window: 10, max: 5 },
  },
}

advanced

advanced.useSecureCookies
boolean
default:"false"
Force the Secure flag on cookies regardless of protocol. Automatically true when baseURL uses https.
advanced.disableCSRFCheck
boolean
default:"false"
Disable all CSRF protection including origin header validation and Fetch Metadata checks.
Disabling CSRF checks exposes your application to CSRF attacks.
advanced.disableOriginCheck
boolean
default:"false"
Disable URL validation for callbackURL, redirectTo, and other redirect targets. Also disables CSRF protection for backward compatibility.
Disabling origin checks opens your app to open-redirect attacks.
advanced.crossSubDomainCookies
{ enabled: boolean; domain: string; additionalCookies?: string[] }
Share session cookies across subdomains.
crossSubDomainCookies: {
  enabled: true,
  domain: "example.com",
}
Custom prefix for all cookie names.
advanced.ipAddress.ipAddressHeaders
string[]
Trusted headers to read the client IP from.
ipAddressHeaders: ["cf-connecting-ip"]
advanced.database.generateId
function | false | 'serial' | 'uuid'
Override the default nanoid-based ID generation.
  • false — let the database generate IDs
  • "serial" — use auto-increment
  • "uuid" — use random UUID
  • function — custom generator (opts: { model, size? }) => string
advanced.backgroundTasks.handler
(promise: Promise<unknown>) => void
Defer non-critical work to run after the response is sent. Pass waitUntil from your serverless platform.
// Vercel
import { waitUntil } from "@vercel/functions";
backgroundTasks: { handler: waitUntil }

// Cloudflare Workers
backgroundTasks: { handler: (p) => ctx.waitUntil(p) }
Enabling background tasks introduces eventual consistency — the response may return optimistic data before the database is updated.
advanced.skipTrailingSlashes
boolean
default:"false"
Treat routes with and without a trailing slash as equivalent.

logger

logger.level
'debug' | 'info' | 'warn' | 'error'
default:"warn"
Minimum log level to output.
logger.disabled
boolean
default:"false"
Suppress all log output.
logger.log
(level, message, ...args) => void
Replace the built-in logger with a custom implementation.
log: (level, message, ...args) => {
  myLoggingService.log({ level, message, metadata: args });
}

databaseHooks

Run code before or after core database operations. The before hook can return modified data; the after hook is fire-and-forget.
databaseHooks: {
  user: {
    create: {
      before: async (user) => {
        return { data: { ...user, role: "member" } };
      },
      after: async (user) => {
        await analytics.track("user_created", { id: user.id });
      },
    },
  },
  session: { /* ... */ },
  account: { /* ... */ },
  verification: { /* ... */ },
}

hooks

Request-level middleware that runs before or after every matched request.
import { createAuthMiddleware } from "better-auth/api";

hooks: {
  before: createAuthMiddleware(async (ctx) => {
    console.log("→", ctx.path);
  }),
  after: createAuthMiddleware(async (ctx) => {
    console.log("←", ctx.context.returned);
  }),
}
See the hooks documentation for full details.

onAPIError

onAPIError.throw
boolean
default:"false"
Re-throw API errors instead of returning an error response.
onAPIError.onError
(error, ctx) => void
Custom error handler invoked on every API error.
onAPIError.errorURL
string
default:"/api/auth/error"
Redirect target for errors that occur in browser flows.
onAPIError.customizeDefaultErrorPage
ErrorPageTheme
Customize colors, sizes, and fonts of the built-in error page at /api/auth/error.

verification

verification.disableCleanup
boolean
default:"false"
Skip deleting expired verification records on read.
verification.storeIdentifier
'plain' | 'hashed' | CustomHasher
How to store verification identifiers (OTP keys, magic-link tokens, etc.).
verification.storeInDatabase
boolean
default:"false"
Write verification records to the primary database even when secondaryStorage is configured.