{"id":"241f7a8085c0b25d","repo":"sindresorhus/got","slug":"the-key-header-must-be-a-single-value","errorCode":null,"errorMessage":"The `${key}` header must be a single value","messagePattern":"The `(.+?)` header must be a single value","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"source/core/index.ts","lineNumber":2190,"sourceCode":"\t\t\t}\n\t\t};\n\n\t\tconst getAuthorizationHeader = (username: string, password: string, isExplicitlyOmitted: boolean) => !isExplicitlyOmitted && (username || password)\n\t\t\t? `Basic ${stringToBase64(`${username}:${password}`)}`\n\t\t\t: undefined;\n\t\tconst sanitizeHeaders = () => {\n\t\t\tconst currentHeaders = options.getInternalHeaders();\n\n\t\t\tfor (const key in currentHeaders) {\n\t\t\t\tif (is.undefined(currentHeaders[key])) {\n\t\t\t\t\toptions.deleteInternalHeader(key);\n\t\t\t\t} else if (is.null(currentHeaders[key])) {\n\t\t\t\t\tthrow new TypeError(`Use \\`undefined\\` instead of \\`null\\` to delete the \\`${key}\\` header`);\n\t\t\t\t} else if (Array.isArray(currentHeaders[key]) && key === 'transfer-encoding') {\n\t\t\t\t\t// Node serializes request header arrays as repeated field lines. Keep framing\n\t\t\t\t\t// unambiguous by allowing only one transfer-encoding value here.\n\t\t\t\t\tif (currentHeaders[key].length !== 1) {\n\t\t\t\t\t\tthrow new TypeError(`The \\`${key}\\` header must be a single value`);\n\t\t\t\t\t}\n\n\t\t\t\t\toptions.setInternalHeader(key, currentHeaders[key][0]);\n\t\t\t\t} else if (Array.isArray(currentHeaders[key]) && singleValueRequestHeaders.has(key)) {\n\t\t\t\t\t// Duplicate credential and content-length lines are not allowed on requests.\n\t\t\t\t\t// Normalize a single-element array to match the long-supported string path.\n\t\t\t\t\tif (currentHeaders[key].length !== 1) {\n\t\t\t\t\t\tthrow new TypeError(`The \\`${key}\\` header must be a single value`);\n\t\t\t\t\t}\n\n\t\t\t\t\toptions.setInternalHeader(key, currentHeaders[key][0]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn currentHeaders;\n\t\t};\n\n\t\tconst getCookieHeader = async (cookieJar: PromiseCookieJar | undefined) => {","sourceCodeStart":2172,"sourceCodeEnd":2208,"githubUrl":"https://github.com/sindresorhus/got/blob/e3924aa1e53a6ca3eb93a43618ce532442a89b40/source/core/index.ts#L2172-L2208","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass transfer-encoding as a single string: `'chunked'` or `'chunked, gzip'` if you genuinely need stacked encodings.","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 ', '.","Let got set transfer-encoding automatically based on the body — do not set it manually for streamed bodies."],"exampleFix":"// before\nawait got.post(url, { headers: { 'transfer-encoding': ['chunked', 'gzip'] }, body: stream });\n\n// after — single value\nawait got.post(url, { headers: { 'transfer-encoding': 'chunked, gzip' }, body: stream });","handlingStrategy":"validation","validationCode":"// Enforce single-value framing headers before the call.\nfunction normalizeFramingHeaders(headers) {\n  const FRAMING = new Set(['transfer-encoding']);\n  for (const [k, v] of Object.entries(headers)) {\n    if (FRAMING.has(k.toLowerCase()) && Array.isArray(v)) {\n      if (v.length === 0) delete headers[k];\n      else if (v.length === 1) headers[k] = v[0];\n      else throw new TypeError(`The \\`${k}\\` header must be a single value`);\n    }\n  }\n  return headers;\n}\nawait got(url, { headers: normalizeFramingHeaders(headers) });","typeGuard":"function isSingleValueHeader(key: string, value: unknown): boolean {\n  if (!Array.isArray(value)) return true;\n  return value.length === 1;\n}","tryCatchPattern":"try {\n  await got(url, { headers });\n} catch (error) {\n  if (error instanceof TypeError && /transfer-encoding.*single value/.test(error.message)) {\n    const fixed = { ...headers, 'transfer-encoding': Array.isArray(headers['transfer-encoding']) ? headers['transfer-encoding'].join(', ') : headers['transfer-encoding'] };\n    return got(url, { headers: fixed });\n  }\n  throw error;\n}","preventionTips":["Pass transfer-encoding as a single string ('chunked' or 'chunked, gzip').","Let got derive transfer-encoding from the body; don't set it manually for streamed bodies.","Normalize header values before the call: unwrap single-element arrays, join multi-element arrays."],"tags":["headers","transfer-encoding","framing","validation"],"analyzedSha":"e3924aa1e53a6ca3eb93a43618ce532442a89b40","analyzedAt":"2026-08-03T19:22:24.770Z","schemaVersion":2}