Dynamic Base URL
Configure Better Auth for preview deployments, multiple domains, and per-request URL resolution.
Use dynamic base URL when your app is served from more than one hostname, such as:
- custom domains like
myapp.comandwww.myapp.com - preview deployments like
my-app-abc123.vercel.app - branch environments like
feature-branch.myapp.com
The configuration itself lives on the baseURL option. This guide focuses on when to use it and how to structure it safely.
Basic Setup
Configure baseURL as an object with an allowedHosts allowlist:
import { betterAuth } from "better-auth"
export const auth = betterAuth({
baseURL: {
allowedHosts: [
"myapp.com",
"www.myapp.com",
"*.vercel.app",
],
},
})When a request comes in, Better Auth extracts the host from x-forwarded-host, host header, or the request URL (in that order), validates it against allowedHosts, and uses the matched value to build the request-specific base URL.
Common Deployment Patterns
Vercel Deployment
export const auth = betterAuth({
baseURL: {
allowedHosts: [
"myapp.com",
"www.myapp.com",
"*.vercel.app",
],
},
})Development + Production
export const auth = betterAuth({
baseURL: {
allowedHosts: [
"localhost:3000",
"localhost:5173",
"myapp.com",
"*.vercel.app",
],
protocol: process.env.NODE_ENV === "development" ? "http" : "https",
},
})Multiple Production Domains
export const auth = betterAuth({
baseURL: {
allowedHosts: [
"myapp.com",
"myapp.co.uk",
"myapp.eu",
],
protocol: "https",
},
})Choosing a Fallback
By default, Better Auth throws if the incoming host does not match allowedHosts. That is usually the safer default because it exposes proxy or deployment mistakes immediately.
If you need a fallback, set one explicitly:
export const auth = betterAuth({
baseURL: {
allowedHosts: ["myapp.com", "*.vercel.app"],
fallback: "https://myapp.com",
},
})Use this only when falling back to a canonical domain is clearly preferable to failing the request.
Cookies Across Subdomains
If you need to share cookies across subdomains, you can enable crossSubDomainCookies while still using dynamic base URL. Better Auth will derive the cookie domain from the resolved host unless you set domain explicitly.
import { betterAuth } from "better-auth"
export const auth = betterAuth({
baseURL: {
allowedHosts: [
"auth.example1.com",
"auth.example2.com",
],
protocol: "https",
},
advanced: {
crossSubDomainCookies: {
enabled: true,
},
},
})If you want to force a shared parent domain instead, set it directly:
import { betterAuth } from "better-auth"
export const auth = betterAuth({
baseURL: {
allowedHosts: ["auth.example1.com", "auth.example2.com"],
protocol: "https",
},
advanced: {
crossSubDomainCookies: {
enabled: true,
domain: ".example.com",
},
},
})See Cookies for the full cookie behavior.
Security Model
Dynamic base URL uses an allowlist model:
- Only hosts listed in
allowedHostsare accepted x-forwarded-hostandhostheaders are sanitized and validated before use- Unknown hosts throw unless you provide
fallback allowedHostsare automatically added totrustedOrigins(localhost entries get bothhttpandhttps)
This keeps multi-domain support explicit instead of trusting arbitrary headers or platform-specific behavior.
Reference
For the exact option shape and property-level behavior, see baseURL in the options reference.