denoland/deno · error · TypeError

Missing content type

Error message

Missing content type

What it means

The null-mime branch of .formData() in ext/fetch/22_body.js: when no Content-Type header is present at all, MIME type parsing yields null and decoding form data is impossible - there is no way to pick a parser. The presence check happens after the multipart/urlencoded branches, so this fires only when the header is entirely absent (contrast with a wrong type, which is the previous error).

Source

Thrown at ext/fetch/22_body.js:472

          if (boundary === null) {
            throw new TypeError(
              "Cannot turn into form data: missing boundary parameter in mime type of multipart form data",
            );
          }
          return parseFormData(chunkToU8(bytes), boundary);
        } else if (essence === "application/x-www-form-urlencoded") {
          // TODO(@AaronO): pass as-is with StringOrBuffer in op-layer
          const entries = parseUrlEncoded(chunkToU8(bytes));
          return formDataFromEntries(
            ArrayPrototypeMap(
              entries,
              (x) => ({ name: x[0], value: x[1] }),
            ),
          );
        }
        throw new TypeError("Body can not be decoded as form data");
      }
      throw new TypeError("Missing content type");
    }
    case "JSON":
      return JSONParse(chunkToString(bytes));
    case "text":
      return chunkToString(bytes);
  }
}

/**
 * @param {BodyInit} object
 * @returns {{body: InnerBody, contentType: string | null}}
 */
function extractBody(object) {
  /** @type {ReadableStream<Uint8Array> | { body: Uint8Array | string, consumed: boolean }} */
  let stream;
  let source = null;
  let length = null;
  let contentType = null;

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Set an explicit Content-Type on the sending side: application/x-www-form-urlencoded for form posts
  2. Guard the call: read the header first and only call formData() when it exists and names a form type
  3. Treat a missing type defensively: reject with 400/415 rather than attempting form parsing

Example fix

// before (client)
await fetch(url, { method: "POST", body: new URLSearchParams({ a: 1 }) });

// after (client)
await fetch(url, {
  method: "POST",
  headers: { "content-type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({ a: 1 }),
});
Defensive patterns

Strategy: validation

Validate before calling

const ct = req.headers.get("content-type");
if (!ct) return new Response("content-type header required", { status: 400 });
const form = await req.formData();

Type guard

function hasContentType(res: Response | Request): boolean {
  return (res.headers.get("content-type") ?? "").trim() !== "";
}

Try / catch

try { return await req.formData(); } catch (e) {
  if (e instanceof TypeError && e.message === "Missing content type") {
    return new Response("content-type header required", { status: 400 });
  }
  throw e;
}

Prevention

When it happens

Trigger: await req.formData() on a request with no Content-Type header: fetch(url, { method: "POST", body }) without a headers object, or a server response that omits the header.

Common situations: Quick fetch() POSTs where the developer forgot headers entirely; proxies (or accidental middleware) stripping the Content-Type; 3rd-party endpoints that answer 200 without any Content-Type.

Related errors


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