Configuration
Configure @nuxtjs/better-auth module options and server auth.
- In `nuxt.config.ts`, set `auth.redirects` (login, guest, authenticated, logout) and `auth.preserveRedirect`
- Use `routeRules` or `nitro.routeRules` to define per-route auth: `{ auth: { only: 'user', redirectTo: '/login' } }`
- In `server/auth.config.ts`, use `defineServerAuth` (object or function syntax) to configure plugins and providers
- The function syntax receives `ctx` with `runtimeConfig` and `db` (NuxtHub)
- The module auto-injects `secret` and `baseURL` — do not set them in defineServerAuth
- Production base URL: runtimeConfig > platform env vars. Request URL inference is development-only.
- Set `NUXT_PUBLIC_SITE_URL` for Cloudflare Workers, custom domains, and production hosts without a supported platform URL variable
Use this page when the module is installed and you want to control runtime behavior in nuxt.config.ts, server/auth.config.ts, and app/auth.config.ts.
Module Configuration
export default defineNuxtConfig({
modules: ['@nuxtjs/better-auth'],
auth: {
redirects: {
login: '/login',
guest: '/',
// authenticated: '/app', // optional
// logout: '/goodbye', // optional
},
preserveRedirect: true,
redirectQueryKey: 'redirect',
},
routeRules: {
'/app/**': { auth: { only: 'user', redirectTo: '/login' } },
'/login': { auth: { only: 'guest', redirectTo: '/app' } },
},
})
routeRules or nitro.routeRules. The module supports both. If both are set, it uses nitro.routeRules.falseEnable client-only mode for external auth backends. When true:- Skips
server/auth.config.tsrequirement - Skips server-side setup (API handlers, middleware, schema generation, devtools)
- Skips secret validation
'server/auth.config'Path to the server auth config file. Relative paths resolve from the Nuxt layer that declares this option. Paths declared by the app resolve from the project root.'app/auth.config'Path to the client auth config file. Relative paths resolve from the Nuxt layer that declares this option. Paths declared by the app resolve from the project root.{ login: '/login', guest: '/' }Global redirect fallbacks:login: where to redirect unauthenticated usersguest: where to redirect authenticated users trying to access guest-only routesauthenticated: where to navigate after successful authenticatedsignIn/signUpwhen noonSuccesscallback is providedlogout: where to navigate after logout (no default)
redirectTo takes precedence when set.trueWhen redirecting unauthenticated users to a login route, append the original requested path as a query param.Configure redirect targets per-route with routeRules.auth.redirectTo or definePageMeta({ auth: { redirectTo } }).'redirect'Query param key used when preserveRedirect is enabled.falseConfigure secondary storage for sessions.true— Temporarily logs a setup warning and continues without module-provided secondary storage because NuxtHub KV cannot implement the required atomicgetAndDeleteandincrementoperations. Sessions use the configured database when one exists, and Better Auth rate limiting uses process-local memory by default.'custom'— You provide an atomicsecondaryStorageindefineServerAuth()(required; the build fails in production if missing). The module won't inject NuxtHub KV, and this mode can omit thesessiontable from generated schema.false(default) — No secondary storage from the module. User-providedsecondaryStorageindefineServerAuth()is not overridden.
falsePluralize table names (user → users)camelCaseColumn/table name casing. Falls back to hub.db.casing when not specified.Redirect Targets (RouteRules-First)
Prefer redirect paths on route-level auth config:
export default defineNuxtConfig({
routeRules: {
'/app/**': { auth: { only: 'user', redirectTo: '/login' } },
'/login': { auth: { only: 'guest', redirectTo: '/app' } },
},
})
You can use the same auth route rules under nitro.routeRules:
export default defineNuxtConfig({
nitro: {
routeRules: {
'/app/**': { auth: { only: 'user', redirectTo: '/login' } },
'/login': { auth: { only: 'guest', redirectTo: '/app' } },
},
},
})
If redirectTo is omitted, shorthand fallbacks apply:
auth: 'user'falls back to/loginauth: 'guest'falls back to/
If you want global defaults for those fallbacks, use auth.redirects.
Default post-auth navigation:
auth.redirects.authenticatedis used whensignIn/signUpcomplete with an authenticated session and no explicitonSuccess.- If a preserved redirect query param is present and safe (
?redirect=), it takes precedence overauth.redirects.authenticated.
Server Configuration
Define your authentication logic in server/auth.config.ts, including plugins, providers, and settings.
defineServerAuth
Use the defineServerAuth helper to ensure type safety and access context. It accepts an object or function syntax.
import { defineServerAuth } from '@nuxtjs/better-auth/config'
// Object syntax (simplest)
export default defineServerAuth({
emailAndPassword: { enabled: true }
})
// Function syntax (access context)
export default defineServerAuth((ctx) => ({
emailAndPassword: { enabled: true }
}))
secret and baseURL. Use Better Auth's secrets option only when you need versioned rotation.- Secret: Priority:
nuxt.config.tsruntimeConfig >NUXT_BETTER_AUTH_SECRET>BETTER_AUTH_SECRET - Versioned secrets: Set
secretsindefineServerAuthor useBETTER_AUTH_SECRETS. When present, the singular secret is a legacy decryption fallback. - Base URL: The module can use
VERCEL_URL(Vercel),CF_PAGES_URL(Cloudflare Pages), orURL(Netlify) at runtime. SetNUXT_PUBLIC_SITE_URLfor Cloudflare Workers, custom domains, and hosts without one of these variables
Context Options
When using the function syntax, defineServerAuth callback receives a context object with useful properties:
import { defineServerAuth } from '@nuxtjs/better-auth/config'
export default defineServerAuth((ctx) => ({
emailAndPassword: { enabled: true },
appName: ctx.runtimeConfig.public.siteUrl ? 'Better Auth App' : 'Better Auth',
}))
ctx.runtimeConfig: Nuxt runtime config.ctx.db: NuxtHub database connection when NuxtHub DB is enabled. Do not setdatabasewhen using module-managed adapters.ctx.requestOrigin: Current request origin when auth is created withserverAuth(event). This is request-controlled context, not a validated canonical URL.
Configure additional trusted origins explicitly. Do not copy requestOrigin into trustedOrigins without validating it against application-owned origins:
import { defineServerAuth } from '@nuxtjs/better-auth/config'
export default defineServerAuth({
trustedOrigins: ['https://app.example.com'],
})
runtimeConfig.public.siteUrl. requestOrigin is optional and only available when auth is created from a request event. The auth instance can be cached for its canonical URL, so requestOrigin describes the request that created it. If allowed origins vary per request, use Better Auth's trustedOrigins function form with your own origin validation.Session Enrichment
You can enrich session payloads with Better Auth's custom-session plugin through plugins in defineServerAuth. This module does not provide a separate requestSession.enrich option.
See the full recipe in Server Utilities.
Base URL Configuration
The module resolves siteUrl using this priority:
| Priority | Source | When Used |
|---|---|---|
| 1 | runtimeConfig.public.siteUrl | Explicit config (always wins) |
| 2 | Request URL, then Nitro development environment | Development only |
| 3 | VERCEL_URL, CF_PAGES_URL, URL | Runtime platform env vars (Vercel, Cloudflare Pages, Netlify) |
| 4 | http://localhost:3000 | Development only |
In production, request headers and listener settings such as HOST and NITRO_HOST never select the auth base URL. Set a public URL explicitly or use a platform environment variable. Without either, auth initialization fails with a configuration error.
Cloudflare Workers does not supply CF_PAGES_URL. Set NUXT_PUBLIC_SITE_URL in the Worker's runtime variables, including when using a workers.dev domain.
Continue passing event to serverAuth(event) for request-scoped database access and the optional ctx.requestOrigin context.
Set an explicit site URL in nuxt.config.ts for deterministic OAuth callbacks and origin checks:
export default defineNuxtConfig({
runtimeConfig: {
public: {
siteUrl: '', // Set NUXT_PUBLIC_SITE_URL at runtime.
},
},
})
Use NUXT_PUBLIC_SITE_URL to provide this value per environment.
Custom domains or self-hosted: You should set runtimeConfig.public.siteUrl (or NUXT_PUBLIC_SITE_URL) when using custom domains or deploying to your own VPS/server. Platform env vars return auto-generated URLs, not your custom domain.
NUXT_PUBLIC_SITE_URL="https://your-domain.com"
Host or forwarded headers must set NUXT_PUBLIC_SITE_URL, unless a platform URL is available. This prevents a supplied host from changing password-reset links or OAuth callbacks. Development still infers the request URL. Custom domains need an explicit URL because platform variables name the platform domain.Runtime Config
Configure secrets using environment variables (see Installation).
NUXT_BETTER_AUTH_SECRET="your-super-secret-key"
NUXT_PUBLIC_SITE_URL="https://your-domain.com" # Required on Workers, custom domains, and hosts without a platform URL
NUXT_BETTER_AUTH_SECRET as the primary secret variable. BETTER_AUTH_SECRET remains supported as a fallback for existing setups.For non-destructive rotation, use BETTER_AUTH_SECRETS=2:current-secret-must-be-at-least-32-characters,1:previous-secret-must-be-at-least-32-characters or configure secrets in defineServerAuth.
Add plugins from a Nuxt layer
A layer can add server and client plugins without changing the app's auth config. Export each plugin as the default value of its own module, then list those modules in the layer config.
export default defineNuxtConfig({
auth: {
serverPluginSources: ['./server/polar-auth-plugin'],
clientPluginSources: ['./app/polar-auth-plugin'],
},
})
import { polar } from '@polar-sh/better-auth'
export default polar({ /* Polar options */ })
import { polarClient } from '@polar-sh/better-auth/client'
export default polarClient()
Relative source paths resolve from the layer that declares them. The module appends project sources, then extended-layer sources in Nuxt priority order, after the plugins in the app's selected auth config.
Contributions are additive and are not deduplicated. Declare each plugin in one place. Do not repeat a plugin in the app auth config, a layer source, or a module registration.
Recommended order
- Configure the module in
nuxt.config.ts. - Configure Better Auth behavior in
server/auth.config.ts. - Configure client plugins in
app/auth.config.ts. - Add route protection with
routeRulesordefinePageMeta({ auth }).
Verify the result
After configuration changes:
- restart the Nuxt dev server if you changed schema- or plugin-related settings
- confirm your app boots without missing-config errors
- confirm route protection works on at least one protected page and one protected API route
For module authors
Other Nuxt modules can register plugin source files. Register the hook during the module's setup function and resolve each file to an absolute path. Registering the hook from modules:done is too late because Better Auth collects the sources from its own modules:done callback.
import { fileURLToPath } from 'node:url'
export default defineNuxtModule({
setup(_options, nuxt) {
nuxt.hook('better-auth:plugins:extend', (sources) => {
sources.server ||= []
sources.client ||= []
sources.server.push(fileURLToPath(new URL('./runtime/server-plugin', import.meta.url)))
sources.client.push(fileURLToPath(new URL('./runtime/client-plugin', import.meta.url)))
})
},
})
The module collects registrations after all Nuxt modules install, then appends them after layer sources. Schema generation, the running auth instances, and generated types import the same ordered plugin modules. Module registrations are additive and are not deduplicated.
The existing better-auth:config:extend hook remains available for schema-only contributions:
nuxt.hook('better-auth:config:extend', (config) => {
config.plugins = [...(config.plugins || []), schemaPlugin]
})
This build-time hook accepts only plugins and does not install them in the running Better Auth instance. Use better-auth:plugins:extend when the plugin must also affect runtime behavior or generated types.
Access sessions from server handlers:
const { user, session } = await getUserSession(event)
if (!user) throw createError({ statusCode: 401 })