sindresorhus/got · error · TypeError

HTTP/2 pseudo-headers are not supported in `options.headers`

Error message

HTTP/2 pseudo-headers are not supported in `options.headers`: ${name}

What it means

Thrown at source/core/options.ts:1125 by `assertValidHeaderName`. HTTP/2 reserves header names starting with `:` (pseudo-headers like `:method`, `:path`, `:authority`, `:scheme`) — these are derived automatically by the HTTP/2 layer and must not be set by the user. The check rejects any header name beginning with `:` regardless of HTTP version, because passing such a header would either be silently dropped (HTTP/1) or conflict with the protocol's own pseudo-headers (HTTP/2).

Source

Thrown at source/core/options.ts:1125

		} finally {
			options.allowAbsoluteUrls = allowAbsoluteUrls;
		}
	}

	if (username !== undefined) {
		options.username = username;
	}

	if (password !== undefined) {
		options.password = password;
	}

	return options.url as URL;
}

function assertValidHeaderName(name: string): void {
	if (name.startsWith(':')) {
		throw new TypeError(`HTTP/2 pseudo-headers are not supported in \`options.headers\`: ${name}`);
	}
}

/**
Safely assign own properties from source to target, skipping `__proto__` to prevent prototype pollution from JSON.parse'd input.
*/
function safeObjectAssign<Target extends Record<string, unknown>, Source extends Record<string, unknown>>(target: Target, source: Source): void {
	for (const [key, value] of Object.entries(source)) {
		if (key === '__proto__') {
			continue;
		}

		Reflect.set(target, key, value);
	}
}

const isToughCookieJar = (cookieJar: PromiseCookieJar | ToughCookieJar): cookieJar is ToughCookieJar => cookieJar.setCookie.length === 4 && cookieJar.getCookieString.length === 0;

View on GitHub (pinned to e3924aa1e5)

Solutions

  1. Remove pseudo-headers (`:method`, `:path`, `:scheme`, `:authority`) from your headers object — they are set by the protocol.
  2. For the equivalent control in HTTP/1, use the corresponding options: `method`, `url`/`path`, and the `host` header.
  3. Filter incoming header maps before passing them: `Object.fromEntries(Object.entries(h).filter(([k]) => !k.startsWith(':')))`.

Example fix

// before
await got(url, { headers: { ':authority': 'api.example.com', 'user-agent': 'x' } });

// after — use regular headers only
await got(url, { headers: { host: 'api.example.com', 'user-agent': 'x' } });

// filter pseudo-headers from upstream source
const headers = Object.fromEntries(
  Object.entries(rawHeaders).filter(([k]) => !k.startsWith(':'))
);
Defensive patterns

Strategy: validation

Validate before calling

function stripPseudoHeaders(headers) {
  for (const key of Object.keys(headers)) {
    if (key.startsWith(':')) {
      throw new TypeError(`HTTP/2 pseudo-headers are not supported in options.headers: ${key}`);
    }
  }
  return headers;
}
await got(url, { headers: stripPseudoHeaders(headers) });

Type guard

function hasPseudoHeader(headers: Record<string, unknown>): boolean {
  return Object.keys(headers).some(k => k.startsWith(':'));
}

Try / catch

try {
  await got(url, { headers });
} catch (error) {
  if (error instanceof TypeError && /HTTP\/2 pseudo-headers are not supported/.test(error.message)) {
    const cleaned = Object.fromEntries(Object.entries(headers).filter(([k]) => !k.startsWith(':')));
    return got(url, { headers: cleaned });
  }
  throw error;
}

Prevention

When it happens

Trigger: Passing `headers: { ':authority': 'x' }`, `headers: { ':method': 'GET' }`, or any header key starting with a colon; programmatically building headers from a map that includes pseudo-headers; copy-pasting HTTP/2 traces into request code.

Common situations: Migrating from gRPC/HTTP2 debug traces into a got client; building headers from a generic serializer that includes pseudo-headers; tooling that surfaces `:path` as if it were a regular header.

Related errors


AI-assisted analysis of sindresorhus/got@e3924aa1e5 (2026-08-03). Data as JSON: /data/errors/2cf40f991ee9ac48.json. Report an issue: GitHub.