sindresorhus/got · error · TypeError

The `${key}` header must be a single value

Error message

The `${key}` header must be a single value

What it means

Thrown at source/core/index.ts:2190 in `sanitizeHeaders`. The `transfer-encoding` header controls HTTP message framing, and Node serializes header arrays as repeated field lines. To keep framing unambiguous, got insists that transfer-encoding be a single string value, not an array. If you pass `transfer-encoding: ['chunked', 'gzip']` (or any array whose length isn't exactly 1), got throws. The single-element array case is permitted and normalized down to a string.

Source

Thrown at source/core/index.ts:2190

			}
		};

		const getAuthorizationHeader = (username: string, password: string, isExplicitlyOmitted: boolean) => !isExplicitlyOmitted && (username || password)
			? `Basic ${stringToBase64(`${username}:${password}`)}`
			: undefined;
		const sanitizeHeaders = () => {
			const currentHeaders = options.getInternalHeaders();

			for (const key in currentHeaders) {
				if (is.undefined(currentHeaders[key])) {
					options.deleteInternalHeader(key);
				} else if (is.null(currentHeaders[key])) {
					throw new TypeError(`Use \`undefined\` instead of \`null\` to delete the \`${key}\` header`);
				} else if (Array.isArray(currentHeaders[key]) && key === 'transfer-encoding') {
					// Node serializes request header arrays as repeated field lines. Keep framing
					// unambiguous by allowing only one transfer-encoding value here.
					if (currentHeaders[key].length !== 1) {
						throw new TypeError(`The \`${key}\` header must be a single value`);
					}

					options.setInternalHeader(key, currentHeaders[key][0]);
				} else if (Array.isArray(currentHeaders[key]) && singleValueRequestHeaders.has(key)) {
					// Duplicate credential and content-length lines are not allowed on requests.
					// Normalize a single-element array to match the long-supported string path.
					if (currentHeaders[key].length !== 1) {
						throw new TypeError(`The \`${key}\` header must be a single value`);
					}

					options.setInternalHeader(key, currentHeaders[key][0]);
				}
			}

			return currentHeaders;
		};

		const getCookieHeader = async (cookieJar: PromiseCookieJar | undefined) => {

View on GitHub (pinned to e3924aa1e5)

Solutions

  1. Pass transfer-encoding as a single string: `'chunked'` or `'chunked, gzip'` if you genuinely need stacked encodings.
  2. Normalize header values before passing to got: if a value is an array of length 1, unwrap it; if length > 1 for transfer-encoding, join with ', '.
  3. Let got set transfer-encoding automatically based on the body — do not set it manually for streamed bodies.

Example fix

// before
await got.post(url, { headers: { 'transfer-encoding': ['chunked', 'gzip'] }, body: stream });

// after — single value
await got.post(url, { headers: { 'transfer-encoding': 'chunked, gzip' }, body: stream });
Defensive patterns

Strategy: validation

Validate before calling

// Enforce single-value framing headers before the call.
function normalizeFramingHeaders(headers) {
  const FRAMING = new Set(['transfer-encoding']);
  for (const [k, v] of Object.entries(headers)) {
    if (FRAMING.has(k.toLowerCase()) && Array.isArray(v)) {
      if (v.length === 0) delete headers[k];
      else if (v.length === 1) headers[k] = v[0];
      else throw new TypeError(`The \`${k}\` header must be a single value`);
    }
  }
  return headers;
}
await got(url, { headers: normalizeFramingHeaders(headers) });

Type guard

function isSingleValueHeader(key: string, value: unknown): boolean {
  if (!Array.isArray(value)) return true;
  return value.length === 1;
}

Try / catch

try {
  await got(url, { headers });
} catch (error) {
  if (error instanceof TypeError && /transfer-encoding.*single value/.test(error.message)) {
    const fixed = { ...headers, 'transfer-encoding': Array.isArray(headers['transfer-encoding']) ? headers['transfer-encoding'].join(', ') : headers['transfer-encoding'] };
    return got(url, { headers: fixed });
  }
  throw error;
}

Prevention

When it happens

Trigger: Passing `headers: { 'transfer-encoding': ['chunked', 'gzip'] }` (stacked encodings as an array); producing headers dynamically and accidentally wrapping transfer-encoding in an array; copy-pasting server-side header handling that tolerates arrays.

Common situations: Custom serialization layers that wrap all headers in arrays for uniformity; proxy-style code forwarding arbitrary header arrays; misconfigured middleware that adds an encoding to an existing array.

Related errors


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