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:

auth.ts
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:

PatternMatches
myapp.comExact match only
*.vercel.appAny subdomain of vercel.app
preview-*.myapp.comSubdomains starting with preview-
localhost:*localhost on any port
auth.ts
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:

auth.ts
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:

auth.ts
export const auth = betterAuth({
    baseURL: {
        allowedHosts: ["myapp.com", "localhost:3000"],
        protocol: "https", // Always use HTTPS
    },
})
ValueBehavior
"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 setSame 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:

auth.ts
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:

auth.ts
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:

auth.ts
export const auth = betterAuth({
    baseURL: "https://myapp.com",
})

This is recommended when your application runs on a single, known URL.

Common Patterns

Vercel Deployment

auth.ts
export const auth = betterAuth({
    baseURL: {
        allowedHosts: [
            "myapp.com",        // Production custom domain
            "www.myapp.com",    // WWW variant
            "*.vercel.app",     // All preview deployments
        ],
    },
})

Development + Production

auth.ts
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

auth.ts
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.

auth.ts
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.com sets cookies with Domain=auth.example1.com
  • A request to auth.example2.com sets cookies with Domain=auth.example2.com

If you want to use the same domain for all hosts, you can set domain explicitly:

auth.ts
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.

  1. Allowlist is mandatory - You cannot accept arbitrary hosts
  2. Headers are validated - The x-forwarded-host and host headers are sanitized before use
  3. Explicit patterns only - No automatic platform detection; you must add each pattern
  4. 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

PropertyTypeDefaultDescription
allowedHostsstring[]RequiredList of allowed host patterns
fallbackstring-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