denoland/deno · error · TypeError

Cannot construct MultipartParser: multipart/form-data must…

Error message

Cannot construct MultipartParser: multipart/form-data must provide a boundary

What it means

Thrown by MultipartParser's constructor (ext/fetch/21_formdata.js) when the boundary argument is falsy. The parser is created by request.formData()/response.formData() when the Content-Type is multipart/form-data; if no boundary parameter was supplied, there is no way to split parts, so parsing aborts immediately.

Solutions

  1. Send `Content-Type: multipart/form-data; boundary=----formdata-deno` (any token boundary) and use that same boundary in the body delimiters.
  2. Better: let the client library generate the body — `const fd = new FormData(); fetch(url, { method: "POST", body: fd })` sets the boundary automatically.
  3. In servers, validate the content type before calling formData() and reply 400 with a clear message when boundary is missing.

Example fix

// before (manual request)
await fetch(url, {
  method: "POST",
  headers: { "content-type": "multipart/form-data" }, // no boundary
  body: handMadeBody,
});

// after
const fd = new FormData();
fd.set("file", new Blob([bytes]), "a.bin");
await fetch(url, { method: "POST", body: fd }); // boundary auto-set
Defensive patterns

Strategy: validation

Validate before calling

function hasMultipartBoundary(req) {
  const ct = req.headers.get("content-type") ?? "";
  return /multipart\/form-data/i.test(ct) && /boundary=/i.test(ct);
}
if (!hasMultipartBoundary(req)) return new Response("multipart boundary required", { status: 400 });
const form = await req.formData();

Type guard

function isMultipartWithBoundary contentTypeHeader(req: Request): boolean {
  const ct = req.headers.get("content-type")?.toLowerCase() ?? "";
  return ct.startsWith("multipart/form-data") && ct.includes("boundary=");
}

Try / catch

try { await req.formData(); } catch (e) { if (e instanceof TypeError && e.message.includes("boundary")) return new Response("bad content type", { status: 400 }); throw e; }

Prevention

When it happens

Trigger: `await req.formData()` where Content-Type is exactly `multipart/form-data` (no `; boundary=...`). Also calling new MultipartParser(body) directly without a boundary in internal code.

Common situations: Hand-crafted multipart requests (curl --data with a manually set content type); a gateway/proxy rewriting or stripping the content type; client code that copies a hardcoded content-type string and forgets the boundary; test fixtures built by hand.

Related errors


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

Appendix: source

Thrown at ext/fetch/21_formdata.js:411

const MAX_MULTIPART_PART_HEADER_SIZE = 16 * 1024;
const MAX_MULTIPART_PART_HEADER_COUNT = 128;

/**
 * @param {Uint8Array} bytes
 * @returns {string}
 */
function decodeLatin1Bytes(bytes) {
  return ReflectApply(StringFromCharCode, null, bytes);
}

class MultipartParser {
  /**
   * @param {Uint8Array} body
   * @param {string | undefined} boundary
   */
  constructor(body, boundary) {
    if (!boundary) {
      throw new TypeError(
        "Cannot construct MultipartParser: multipart/form-data must provide a boundary",
      );
    }

    this.body = body;
    this.boundaryChars = core.encode(`--${boundary}`);
  }

  /**
   * @param {string} headersText
   * @returns {{ headers: Headers, disposition: Map<string, string> }}
   */
  #parseHeaders(headersText) {
    const headers = new Headers();
    const rawHeaders = StringPrototypeSplit(headersText, "\r\n");
    let headerCount = 0;
    for (let i = 0; i < rawHeaders.length; ++i) {
      const rawHeader = rawHeaders[i];

View on GitHub (pinned to 9ad36f7a2c)