nextauthjs/next-auth · error · TypeError
option domain is invalid: ${options.domain}
Error message
option domain is invalid: ${options.domain} What it means
serialize() validates the optional `domain` option against domainValueRegExp before appending `; Domain=` to the Set-Cookie string. If the value is present but not a syntactically valid domain attribute (e.g. contains illegal characters, spaces, or an invalid form), a TypeError is thrown immediately. This is a fail-fast guard so malformed cookie attributes never reach the browser.
Source
Thrown at packages/core/src/lib/vendored/cookie.ts:284
if (!cookieValueRegExp.test(value)) {
throw new TypeError(`argument val is invalid: ${val}`)
}
let str = name + "=" + value
if (!options) return str
if (options.maxAge !== undefined) {
if (!Number.isInteger(options.maxAge)) {
throw new TypeError(`option maxAge is invalid: ${options.maxAge}`)
}
str += "; Max-Age=" + options.maxAge
}
if (options.domain) {
if (!domainValueRegExp.test(options.domain)) {
throw new TypeError(`option domain is invalid: ${options.domain}`)
}
str += "; Domain=" + options.domain
}
if (options.path) {
if (!pathValueRegExp.test(options.path)) {
throw new TypeError(`option path is invalid: ${options.path}`)
}
str += "; Path=" + options.path
}
if (options.expires) {
if (
!isDate(options.expires) ||
!Number.isFinite(options.expires.valueOf())
) {View on GitHub (pinned to a1a16a5a77)
Solutions
- Pass only the bare hostname, e.g. domain: 'example.com' (leading dot is allowed but unnecessary for host-only matching)
- Strip protocol/path/port: new URL(configuredDomain).hostname
- Validate with a regex before calling serialize
- If the domain is dynamic/user-supplied, reject invalid values upstream instead of letting serialize throw
Example fix
// before
serialize('sid', val, { domain: process.env.COOKIE_DOMAIN }) // 'https://example.com'
// after
const { hostname } = new URL(process.env.COOKIE_DOMAIN)
serialize('sid', val, { domain: hostname }) Defensive patterns
Strategy: validation
Validate before calling
function isValidCookieDomain(d) {
return typeof d === 'string' && /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$/.test(d.replace(/^\./, ''))
}
if (opts.domain && !isValidCookieDomain(opts.domain)) throw new TypeError(`option domain is invalid: ${opts.domain}`) Type guard
function isCookieDomain(v: unknown): v is string {
return typeof v === 'string' && v.length > 0 && !/[^a-zA-Z0-9.\-]/.test(v)
} Try / catch
let cookie
try {
cookie = serialize('sid', val, { domain })
} catch (err) {
if (err instanceof TypeError && err.message.startsWith('option domain is invalid')) {
throw new ConfigError(`Bad COOKIE_DOMAIN '${domain}' — use a bare hostname, not a URL`)
}
throw err
} Prevention
- Always pass a bare hostname, never a URL or host:port
- Derive the domain with new URL(input).hostname when sourcing from config
- Validate env/config values at application startup, not at request time
- Unit-test serialize calls with every configurable cookie option
When it happens
Trigger: Calling serialize(name, val, { domain: 'not a domain!' }) or any domain containing characters outside the allowed domain-value pattern — e.g. trailing dots/slashes, underscores, spaces, full URLs like 'https://example.com', or an empty-but-truthy invalid string.
Common situations: Reading the domain from an env var or config that holds a full URL instead of a bare hostname; concatenating a port ('example.com:3000' — ports are not allowed in cookie domains); user-supplied input passed through unvalidated; typos like 'exam ple.com'.
Related errors
- option path is invalid: ${options.path}
- option expires is invalid: ${options.expires}
- option priority is invalid: ${options.priority}
- option sameSite is invalid: ${options.sameSite}
- argument name is invalid: ${name}
AI-assisted analysis of nextauthjs/next-auth@a1a16a5a77 (2026-08-28).
Data as JSON: /api/errors/c85960cacc2e98df.
Report an issue: GitHub.