denoland/deno · error · TypeError

Cannot change header: headers are immutable

Error message

Cannot change header: headers are immutable

What it means

Headers objects carry a guard; when it is "immutable" - most notably on responses returned by fetch() - every mutation method that goes through appendHeader (append, set) throws. The name/value checks run first, so a bad name on an immutable header reports the name error instead.

Solutions

  1. Copy into a mutable Headers: `const h = new Headers(res.headers); h.set("x", "y")`
  2. Rebuild the response: `new Response(res.body, { status: res.status, headers: h })`
  3. Set custom headers when constructing a new Response instead of mutating a received one

Example fix

// before
const res = await fetch(url);
res.headers.set("x-cache", "miss"); // immutable -> throws

// after
const res = await fetch(url);
const headers = new Headers(res.headers);
headers.set("x-cache", "miss");
const out = new Response(res.body, { status: res.status, headers });
Defensive patterns

Strategy: fallback

Validate before calling

function mutableCopy(res: Response): Headers {
  const h = new Headers(res.headers); // fresh list, guard 'none'
  return h;
}

Prevention

When it happens

Trigger: `(await fetch(url)).headers.set("x", "y")`; calling append/delete on headers of a received response.

Common situations: Middleware trying to add auth or CORS headers to a fetched response before returning it; frameworks mutating a response they did not construct.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/f1cc85c402e0137b. Report an issue: GitHub.

Appendix: source

Thrown at ext/fetch/20_headers.js:181

 * @param {Headers} headers
 * @param {string} name
 * @param {string} value
 */
function appendHeader(headers, name, value) {
  // 1.
  value = normalizeHeaderValue(value);

  // 2.
  if (!checkHeaderNameForHttpTokenCodePoint(name)) {
    throw new TypeError(`Invalid header name: "${name}"`);
  }
  if (!checkForInvalidValueChars(value)) {
    throw new TypeError(`Invalid header value: "${value}"`);
  }

  // 3.
  if (headers[_guard] == "immutable") {
    throw new TypeError("Cannot change header: headers are immutable");
  }

  // 7.
  const list = headerListFromHeaders(headers);
  const lowerNames = ensureLowerNames(headers);
  const lowercaseName = byteLowerCase(name);
  for (let i = 0; i < lowerNames.length; i++) {
    if (lowerNames[i] === lowercaseName) {
      name = list[i][0];
      break;
    }
  }
  ArrayPrototypePush(list, [name, value]);
  ArrayPrototypePush(lowerNames, lowercaseName);
}

function appendHeaderToList(list, name, value) {
  value = normalizeHeaderValue(value);

View on GitHub (pinned to 9ad36f7a2c)