Dynamic Base URL
Configure Better Auth to work with multiple domains and preview deployments.
When deploying to platforms like Vercel, your application can run on multiple URLs simultaneously:
- Custom domains (e.g.,
myapp.com,www.myapp.com) - Preview deployments (e.g.,
my-app-abc123.vercel.app) - Branch previews (e.g.,
feature-branch.myapp.com)
Better Auth supports dynamic base URL resolution using an allowlist-based approach that determines the correct URL from each incoming request.
Basic Usage
Configure baseURL as an object with an allowedHosts array:
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 the x-forwarded-host or host header and validates it against your allowlist. If the host matches, that URL is used as the base URL for the request.
Wildcard Patterns
The allowedHosts array supports wildcard patterns:
| Pattern | Matches |
|---|---|
myapp.com | Exact match only |
*.vercel.app | Any subdomain of vercel.app |
preview-*.myapp.com | Subdomains starting with preview- |
localhost:* | localhost on any port |
export const auth = betterAuth({
baseURL: {
allowedHosts: [
"myapp.com", // Production
"*.vercel.app", // All Vercel previews
"preview-*.myapp.com", // Custom preview subdomains
"localhost:3000", // Local development
],
},
})Fallback URL
By default, if the incoming host doesn't match any pattern in allowedHosts, Better Auth throws an error. You can provide a fallback URL instead:
export const auth = betterAuth({
baseURL: {
allowedHosts: ["myapp.com", "*.vercel.app"],
fallback: "https://myapp.com",
},
})With a fallback configured:
- Matching hosts use the derived URL
- Non-matching hosts use the fallback URL (no error thrown)
Use fallbacks carefully. Silent fallbacks can mask misconfigurations. Consider whether throwing an error (the default) is more appropriate for your use case.
Protocol Configuration
Control the protocol used when constructing URLs:
export const auth = betterAuth({
baseURL: {
allowedHosts: ["myapp.com", "localhost:3000"],
protocol: "https", // Always use HTTPS
},
})| Value | Behavior |
|---|---|
"https" | Always use HTTPS |
"http" | Always use HTTP |
"auto" | Derive from x-forwarded-proto header, then request URL; defaults to HTTPS if neither is available |
| Not set | Same as "auto" |
The protocol setting controls how the URL is constructed for each request. For the cookie Secure flag, Better Auth uses protocol: "https" → secure, protocol: "http" → not secure, and for "auto" or unset → falls back to NODE_ENV === "production". You can always override this with advanced.useSecureCookies.
For mixed environments (local dev + production), you can use environment variables:
export const auth = betterAuth({
baseURL: {
allowedHosts: ["myapp.com", "localhost:3000"],
protocol: process.env.NODE_ENV === "development" ? "http" : "https",
},
})Trusted Origins Integration
When you configure allowedHosts, Better Auth automatically adds them to trustedOrigins. This means:
export const auth = betterAuth({
baseURL: {
allowedHosts: ["myapp.com", "*.vercel.app"],
},
})
// Automatically adds to trustedOrigins:
// - "https://myapp.com"
// - "https://*.vercel.app"You don't need to duplicate your allowed hosts in both configurations.
Static Base URL (Backwards Compatible)
The traditional static string configuration still works:
export const auth = betterAuth({
baseURL: "https://myapp.com",
})This is recommended when your application runs on a single, known URL.
Common Patterns
Vercel Deployment
export const auth = betterAuth({
baseURL: {
allowedHosts: [
"myapp.com", // Production custom domain
"www.myapp.com", // WWW variant
"*.vercel.app", // All preview deployments
],
},
})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",
],
},
})Cross-Subdomain Cookies
If you need to share cookies across subdomains, you can enable crossSubDomainCookies without specifying a static domain. Better Auth automatically derives the cookie domain from the resolved host of each request.
import { betterAuth } from "better-auth"
export const auth = betterAuth({
baseURL: {
allowedHosts: [
"auth.example1.com",
"auth.example2.com",
],
protocol: "https",
},
advanced: {
crossSubDomainCookies: {
enabled: true,
},
},
})With this configuration:
- A request to
auth.example1.comsets cookies withDomain=auth.example1.com - A request to
auth.example2.comsets cookies withDomain=auth.example2.com
If you want to use the same domain for all hosts, you can 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,
domain: ".example.com", // always use this domain regardless of host
},
},
})With a static baseURL string, crossSubDomainCookies derives the domain from that string at init time. With dynamic base URL, the domain is computed per-request from the resolved host. Learn more about cross-subdomain cookies in the Cookies documentation.
Security Considerations
Dynamic base URL uses an allowlist-based approach for security. Only hosts explicitly listed in allowedHosts are accepted.
- Allowlist is mandatory - You cannot accept arbitrary hosts
- Headers are validated - The
x-forwarded-hostandhostheaders are sanitized before use - Explicit patterns only - No automatic platform detection; you must add each pattern
- Clear errors - Unknown hosts throw descriptive errors (unless fallback is set)
This differs from some other auth libraries that auto-trust platforms or accept any header value. Better Auth prioritizes security over convenience.
Configuration Reference
| Property | Type | Default | Description |
|---|---|---|---|
allowedHosts | string[] | Required | List of allowed host patterns |
fallback | string | - | URL to use when host doesn't match |
protocol | "http" | "https" | "auto" | "auto" | Protocol for URL construction. "auto" derives from request headers, defaults to HTTPS |