denoland/deno · error · NodeTypeError

ERR_INVALID_ARG_TYPE

ERR_INVALID_ARG_TYPE

Error message

The "headers" argument must be an instance of Headers or Map. Received ${headers}

What it means

setHeaders() duck-types its argument: it must be non-null, not an array, and expose callable `.keys()` and `.get()` - in practice a Headers or Map instance. Anything else (plain object, array of pairs, string) throws ERR_INVALID_ARG_TYPE.

Source

Thrown at ext/node/polyfills/_http_outgoing.ts:1159

    },
    writable: true,
    enumerable: true,
    configurable: true,
  },
  setHeaders: {
    __proto__: null,
    value: function setHeaders(headers: any) {
      if (this._header) {
        throw new ERR_HTTP_HEADERS_SENT("set");
      }

      if (
        !headers ||
        ArrayIsArray(headers) ||
        typeof headers.keys !== "function" ||
        typeof headers.get !== "function"
      ) {
        throw new ERR_INVALID_ARG_TYPE(
          "headers",
          ["Headers", "Map"],
          headers,
        );
      }

      let cookies = null;
      const iterator = headers[SymbolIterator]();
      while (true) {
        // deno-lint-ignore deno-internal/prefer-primordials
        const { done, value: entry } = iterator.next();
        if (done) {
          break;
        }
        const key = entry[0];
        const value = entry[1];
        if (key === "set-cookie") {
          if (ArrayIsArray(value)) {

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Wrap entry pairs: res.setHeaders(new Map([['x-a','1']]))
  2. Or wrap an object: res.setHeaders(new Headers({'x-a':'1'}))
  3. Type the parameter as `Headers | Map<string,string>` in your own wrappers so the mistake is caught at compile time

Example fix

// before
res.setHeaders({ 'x-a': '1', 'x-b': '2' }); // throws ERR_INVALID_ARG_TYPE

// after
res.setHeaders(new Map([['x-a','1'], ['x-b','2']]));
// or
res.setHeaders(new Headers({ 'x-a': '1', 'x-b': '2' }));
Defensive patterns

Strategy: type-guard

Validate before calling

const ok = h !== null &&
  !Array.isArray(h) &&
  typeof h === 'object' &&
  typeof h.keys === 'function' &&
  typeof h.get === 'function';
if (ok) res.setHeaders(h);
else res.setHeaders(new Map(Object.entries(h ?? {})));

Type guard

function isHeadersLike(v: unknown): v is Headers | Map<string, string> {
  return !!v && typeof v === 'object' && !Array.isArray(v) &&
    typeof (v as any).keys === 'function' && typeof (v as any).get === 'function';
}

Try / catch

try {
  res.setHeaders(headers);
} catch (e) {
  if (e?.code === 'ERR_INVALID_ARG_TYPE' && /headers/.test(e.message)) {
    res.setHeaders(new Map(Object.entries(headers)));
  } else throw e;
}

Prevention

When it happens

Trigger: res.setHeaders({'x-a': '1'}) (plain object), res.setHeaders([['x-a','1']]) (array of entries), res.setHeaders('x-a=1') (string), or res.setHeaders(null).

Common situations: Developers assuming object-literal semantics from setHeader(); passing fetch-style Headers built wrong; passing a serialized headers array from another layer of the app.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/ca2f9fb25cad8312. Report an issue: GitHub.