Skip to main content
Better Auth is framework-agnostic. It exposes a standard Request / Response handler that integrates with any server framework that supports the Web Fetch API, including Next.js, Nuxt, SvelteKit, Astro, Hono, Elysia, Remix, Express, Fastify, and more.Framework-specific helpers (e.g. toNextJsHandler) are available in the better-auth package for the most common runtimes.
Yes. Better Auth runs on edge runtimes such as Cloudflare Workers and Vercel Edge Functions. Make sure the nodejs_compat compatibility flag is enabled in wrangler.toml for Cloudflare Workers:
wrangler.toml
compatibility_flags = ["nodejs_compat"]
For best results on serverless/edge platforms, pair it with a compatible database (e.g. Cloudflare D1, Turso, Neon) and optionally a KV store for sessions.
Yes. Better Auth is used in production by many teams. It is actively maintained, has a comprehensive test suite, and follows security best practices (CSRF protection, scrypt password hashing, secure cookies, rate limiting, PKCE for OAuth, etc.).
Use the user.additionalFields config option:
auth.ts
export const auth = betterAuth({
  user: {
    additionalFields: {
      role: {
        type: "string",
        input: false, // not settable by the client
      },
    },
  },
});
Set input: false for fields that should not be writable by end users (e.g. role, banned). See the TypeScript docs for type inference on the client.
Use the secrets option (plural) to perform a non-destructive rotation. The first entry becomes the active key; additional entries are decryption-only:
auth.ts
secrets: [
  { version: 2, value: process.env.SECRET_V2! }, // new key
  { version: 1, value: process.env.SECRET_V1! }, // old key (kept for decryption)
]
Existing encrypted data (sessions, tokens) is automatically re-encrypted with the new key on next write. No downtime or database migrations are required. See the security docs for details.
Make sure you are using the correct import path. It differs by environment:
// React frontend
import { createAuthClient } from "better-auth/react";

// Server (Next.js server actions, middleware, API routes)
import { createAuthClient } from "better-auth/client";
authClient.getSession() cannot access cookies in server environments. Use auth.api.getSession and pass the request headers:
server.tsx
import { auth } from "./auth";
import { headers } from "next/headers";

const session = await auth.api.getSession({
  headers: await headers(),
});
If you need to use the auth client on the server, forward the headers via fetchOptions:
const session = await authClient.getSession({
  fetchOptions: { headers: await headers() },
});
  • useSession is a React hook. It triggers re-renders when the session changes and is suitable for reactive UI updates.
  • getSession returns a plain promise. Use it anywhere a hook cannot be called: server components, route handlers, server actions, or non-React environments.
Avoid calling useSession in a layout.tsx file for performance reasons. Prefer auth.api.getSession in server components.
Not currently. The name, email, and image fields are part of the core schema. Greater schema customisation is planned for a future release.
Enable strict mode in your tsconfig.json:
tsconfig.json
{
  "compilerOptions": {
    "strict": true
  }
}
If you cannot use strict, enable strictNullChecks at minimum. See the TypeScript docs for more.
If you see an error like:
No request state found. Please make sure you are calling this function within a `runWithRequestState` callback.
This is usually caused by multiple versions of better-auth or @better-auth/core in your dependency tree.Diagnose:
pnpm why @better-auth/core
pnpm why better-auth
Fix — clean reinstall:
rm -rf node_modules pnpm-lock.yaml
pnpm install
Next.js — add to serverExternalPackages:
next.config.ts
const config = {
  serverExternalPackages: ["better-auth"],
};
Cloudflare Workers — enable Node.js compat:
wrangler.toml
compatibility_flags = ["nodejs_compat"]
Yarn v1 / pnpm v9 — force better-call to a single instance:
package.json
{
  "dependencies": { "better-call": "^1.1.8" },
  "resolutions": { "better-call": "^1.1.8" }
}
Use undici’s ProxyAgent to set a global dispatcher before creating your auth instance:
auth.ts
import { betterAuth } from "better-auth";
import { ProxyAgent, setGlobalDispatcher } from "undici";

setGlobalDispatcher(new ProxyAgent("http://your-proxy.example.com:8080"));

export const auth = betterAuth({ /* ... */ });