docs/content/docs/guides/dynamic-base-url.mdx
Use dynamic base URL when your app is served from more than one hostname, such as:
myapp.com and www.myapp.commy-app-abc123.vercel.appfeature-branch.myapp.comThe configuration itself lives on the baseURL option. This guide focuses on when to use it and how to structure it safely.
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.
export const auth = betterAuth({
baseURL: {
allowedHosts: [
"myapp.com",
"www.myapp.com",
"*.vercel.app",
],
},
})
export const auth = betterAuth({
baseURL: {
allowedHosts: [
"localhost:3000",
"localhost:5173",
"myapp.com",
"*.vercel.app",
],
protocol: process.env.NODE_ENV === "development" ? "http" : "https",
},
})
export const auth = betterAuth({
baseURL: {
allowedHosts: [
"myapp.com",
"myapp.co.uk",
"myapp.eu",
],
protocol: "https",
},
})
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.
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.
Dynamic base URL uses an allowlist model:
allowedHosts are acceptedx-forwarded-host and host headers are sanitized and validated before usefallbackallowedHosts are automatically added to trustedOrigins (localhost entries get both http and https)This keeps multi-domain support explicit instead of trusting arbitrary headers or platform-specific behavior.
For the exact option shape and property-level behavior, see baseURL in the options reference.