{"record":{"id":"5daca81ee5011956","repo":"denoland/deno","slug":"unable-to-parse-body-as-form-data","errorCode":null,"errorMessage":"Unable to parse body as form data","messagePattern":"Unable to parse body as form data","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/fetch/21_formdata.js","lineNumber":490,"sourceCode":"  }\n\n  /**\n   * @returns {FormData}\n   */\n  parse() {\n    // To have fields body must be at least 2 boundaries + \\r\\n + --\n    // on the last boundary.\n    if (this.body.length < (this.boundary.length * 2) + 4) {\n      const decodedBody = core.decode(this.body);\n      const lastBoundary = this.boundary + \"--\";\n      // check if it's an empty valid form data\n      if (\n        decodedBody === lastBoundary ||\n        decodedBody === lastBoundary + \"\\r\\n\"\n      ) {\n        return new FormData();\n      }\n      throw new TypeError(\"Unable to parse body as form data\");\n    }\n\n    const formData = new FormData();\n    let headerText = \"\";\n    let headerStart = 0;\n    let state = 0;\n    let fileStart = 0;\n\n    for (let i = 0; i < this.body.length; i++) {\n      const byte = this.body[i];\n      const prevByte = this.body[i - 1];\n      const isNewLine = byte === LF && prevByte === CR;\n\n      if (state === 0 && isNewLine) {\n        state = 1;\n        headerStart = i + 1;\n      } else if (\n        state === 1","sourceCodeStart":472,"sourceCodeEnd":508,"githubUrl":"https://github.com/denoland/deno/blob/89f33cbef296a2b287f323d42de54c871fa69c77/ext/fetch/21_formdata.js#L472-L508","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Never pre-read or transform the body - call req.formData() exactly once on the original request","Make the sender and receiver agree on the boundary: let the client generate it (FormData does) and the server parse it from Content-Type","Check Content-Length vs actual body and abort/retry truncated uploads before parsing"],"exampleFix":"// before\nconst text = await req.text();\nconst form = parseMultipartManually(text); // boundary/length drift -> this error path\n\n// after\nconst form = await req.formData(); // parser sees the raw bytes and matching boundary","handlingStrategy":"try-catch","validationCode":"// Sender/receiver sanity: boundary in Content-Type must match encoder's\nconst body = buildMultipart(fields, boundary);\nconst resp = await fetch(url, {\n  method: \"POST\",\n  headers: { \"content-type\": `multipart/form-data; boundary=${boundary}` },\n  body,\n});","typeGuard":null,"tryCatchPattern":"try { return await req.formData(); } catch (e) {\n  if (e instanceof TypeError && e.message.includes(\"Unable to parse body as form data\")) {\n    return new Response(\"malformed multipart body\", { status: 400 });\n  }\n  throw e;\n}","preventionTips":["Never pre-read, decode, or re-encode req.body before formData()","Use one boundary constant for building the body and the Content-Type","Abort on short reads (Content-Length mismatch) instead of parsing truncated bodies"],"tags":["fetch","form-data","multipart","parsing"],"backgroundTag":null,"analyzedSha":"89f33cbef296a2b287f323d42de54c871fa69c77","analyzedAt":"2026-08-16T07:54:21.310Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}