denoland/deno · error · TypeError

Cannot construct MultipartParser: multipart/form-data must p

Error message

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

What it means

MultipartParser (internal class in ext/fetch/21_formdata.js) requires a truthy boundary string; its constructor throws immediately otherwise. The public route is request.formData()/response.formData(): 22_body.js extracts boundary from the Content-Type and only rejects a null (absent) parameter, so an empty boundary= value slips through as "" and fails here.

Source

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

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.boundary = `--${boundary}`;
    this.body = body;
    this.boundaryChars = core.encode(this.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) {

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Fix the sending side to always generate a real boundary (browsers and the FormData + fetch path do this automatically)
  2. Validate before parsing: read the Content-Type, confirm the essence is multipart/form-data and the boundary parameter is present and non-empty
  3. Return 400 early when the boundary check fails instead of letting formData() throw

Example fix

// before
const form = await req.formData(); // throws on boundary=

// after
const ct = req.headers.get("content-type") ?? "";
const m = /boundary=([^;]+)/i.exec(ct);
if (!m || m[1].trim() === "") {
  return new Response("bad multipart content-type", { status: 400 });
}
const form = await req.formData();
Defensive patterns

Strategy: validation

Validate before calling

const ct = req.headers.get("content-type") ?? "";
const boundary = /boundary=([^;]+)/i.exec(ct)?.[1]?.trim();
if (!boundary) return new Response("multipart boundary missing", { status: 400 });
const form = await req.formData();

Type guard

function hasMultipartBoundary(contentType: string | null): boolean {
  return contentType != null && /multipart\/form-data/i.test(contentType) &&
    /boundary=[^;]/i.test(contentType) && !/boundary=\s*(;|$)/i.test(contentType);
}

Try / catch

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

Prevention

When it happens

Trigger: A request/response with Content-Type: multipart/form-data; boundary= (empty parameter) reaching .formData(); hand-rolled multipart senders that emit the parameter with no value; malformed proxy rewrites that truncate the Content-Type.

Common situations: Custom test servers or PHP/Python scripts that build multipart Content-Types manually and forget the boundary value; proxies or middlewares that normalize and truncate header parameters; fuzzed input in security tests.

Related errors


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