Getting Started

Configuration

Configure the module options and your Better Auth server instance.
Prompt
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

nuxt.config.ts
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' } },
  },
})
You can define auth route rules in either routeRules or nitro.routeRules. The module supports both. If both are set, it uses nitro.routeRules.
clientOnly
boolean
Default: falseEnable client-only mode for external auth backends. When true:
  • Skips server/auth.config.ts requirement
  • Skips server-side setup (API handlers, middleware, schema generation, devtools)
  • Skips secret validation
Use this when your Better Auth server runs on a separate backend (e.g., standalone h3/Nitro project).See External Auth Backend guide.
serverConfig
string
Default: '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.
clientConfig
string
Default: '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.
redirects
{ login?: string, guest?: string, authenticated?: string, logout?: string }
Default: { login: '/login', guest: '/' }Global redirect fallbacks:
  • login: where to redirect unauthenticated users
  • guest: where to redirect authenticated users trying to access guest-only routes
  • authenticated: where to navigate after successful authenticated signIn / signUp when no onSuccess callback is provided
  • logout: where to navigate after logout (no default)
Per-route redirectTo takes precedence when set.
preserveRedirect
boolean
Default: 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 } }).
redirectQueryKey
string
Default: 'redirect'Query param key used when preserveRedirect is enabled.
hubSecondaryStorage
boolean | 'custom'
Default: 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 atomic getAndDelete and increment operations. Sessions use the configured database when one exists, and Better Auth rate limiting uses process-local memory by default.
  • 'custom' — You provide an atomic secondaryStorage in defineServerAuth() (required; the build fails in production if missing). The module won't inject NuxtHub KV, and this mode can omit the session table from generated schema.
  • false (default) — No secondary storage from the module. User-provided secondaryStorage in defineServerAuth() is not overridden.
schema.usePlural
boolean
Default: falsePluralize table names (user → users)
schema.casing
'camelCase' | 'snake_case'
Default: camelCaseColumn/table name casing. Falls back to hub.db.casing when not specified.
schema.schemaName
string
PostgreSQL schema namespace for generated auth tables.

Redirect Targets (RouteRules-First)

Prefer redirect paths on route-level auth config:

nuxt.config.ts
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:

nuxt.config.ts
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 /login
  • auth: 'guest' falls back to /

If you want global defaults for those fallbacks, use auth.redirects.

Default post-auth navigation:

  • auth.redirects.authenticated is used when signIn / signUp complete with an authenticated session and no explicit onSuccess.
  • If a preserved redirect query param is present and safe (?redirect=), it takes precedence over auth.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.

server/auth.config.ts
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 }
}))
The module automatically injects the singular secret and baseURL. Use Better Auth's secrets option only when you need versioned rotation.
  • Secret: Priority: nuxt.config.ts runtimeConfig > NUXT_BETTER_AUTH_SECRET > BETTER_AUTH_SECRET
  • Versioned secrets: Set secrets in defineServerAuth or use BETTER_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), or URL (Netlify) at runtime. Set NUXT_PUBLIC_SITE_URL for 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:

server/auth.config.ts
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 set database when using module-managed adapters.
  • ctx.requestOrigin: Current request origin when auth is created with serverAuth(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:

server/auth.config.ts
import { defineServerAuth } from '@nuxtjs/better-auth/config'

export default defineServerAuth({
  trustedOrigins: ['https://app.example.com'],
})
For configured canonical URLs, read 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:

PrioritySourceWhen Used
1runtimeConfig.public.siteUrlExplicit config (always wins)
2Request URL, then Nitro development environmentDevelopment only
3VERCEL_URL, CF_PAGES_URL, URLRuntime platform env vars (Vercel, Cloudflare Pages, Netlify)
4http://localhost:3000Development 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:

nuxt.config.ts
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.

.env
NUXT_PUBLIC_SITE_URL="https://your-domain.com"
Migration: deployments that previously inferred the URL from 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).

.env
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
Use 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.

layers/payments/nuxt.config.ts
export default defineNuxtConfig({
  auth: {
    serverPluginSources: ['./server/polar-auth-plugin'],
    clientPluginSources: ['./app/polar-auth-plugin'],
  },
})
layers/payments/server/polar-auth-plugin.ts
import { polar } from '@polar-sh/better-auth'

export default polar({ /* Polar options */ })
layers/payments/app/polar-auth-plugin.ts
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.

  1. Configure the module in nuxt.config.ts.
  2. Configure Better Auth behavior in server/auth.config.ts.
  3. Configure client plugins in app/auth.config.ts.
  4. Add route protection with routeRules or definePageMeta({ 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 })