denoland/deno · error · TypeError

Unable to parse body as form data

Error message

Unable to parse body as form data

What it means

MultipartParser.parseBody() rejects bodies that are too short to contain even two boundary delimiters: length must be >= boundary.length * 2 + 4. The only short bodies accepted are exactly the closing delimiter "--boundary--" (with optional trailing \r\n), i.e. an empty but valid form; everything else throws this TypeError.

Source

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

  }

  /**
   * @returns {FormData}
   */
  parse() {
    // To have fields body must be at least 2 boundaries + \r\n + --
    // on the last boundary.
    if (this.body.length < (this.boundary.length * 2) + 4) {
      const decodedBody = core.decode(this.body);
      const lastBoundary = this.boundary + "--";
      // check if it's an empty valid form data
      if (
        decodedBody === lastBoundary ||
        decodedBody === lastBoundary + "\r\n"
      ) {
        return new FormData();
      }
      throw new TypeError("Unable to parse body as form data");
    }

    const formData = new FormData();
    let headerText = "";
    let headerStart = 0;
    let state = 0;
    let fileStart = 0;

    for (let i = 0; i < this.body.length; i++) {
      const byte = this.body[i];
      const prevByte = this.body[i - 1];
      const isNewLine = byte === LF && prevByte === CR;

      if (state === 0 && isNewLine) {
        state = 1;
        headerStart = i + 1;
      } else if (
        state === 1

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Never pre-read or transform the body - call req.formData() exactly once on the original request
  2. Make the sender and receiver agree on the boundary: let the client generate it (FormData does) and the server parse it from Content-Type
  3. Check Content-Length vs actual body and abort/retry truncated uploads before parsing

Example fix

// before
const text = await req.text();
const form = parseMultipartManually(text); // boundary/length drift -> this error path

// after
const form = await req.formData(); // parser sees the raw bytes and matching boundary
Defensive patterns

Strategy: try-catch

Validate before calling

// Sender/receiver sanity: boundary in Content-Type must match encoder's
const body = buildMultipart(fields, boundary);
const resp = await fetch(url, {
  method: "POST",
  headers: { "content-type": `multipart/form-data; boundary=${boundary}` },
  body,
});

Try / catch

try { return await req.formData(); } catch (e) {
  if (e instanceof TypeError && e.message.includes("Unable to parse body as form data")) {
    return new Response("malformed multipart body", { status: 400 });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling .formData() on a truncated request body (client disconnected mid-upload); a Content-Type boundary= value that does not match the boundary actually used to encode the body; a body that was read/decoded once and re-encoded, changing its bytes.

Common situations: Proxy or middleware that consumes and reconstructs req.body before formData(); mismatched boundary between client library and hand-written server tests; uploads cut off by size limits or timeouts.

Understand the failure class

Related errors


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