{"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":533,"sourceCode":"        continue;\n      }\n\n      const delimiter = this.#matchDelimiter(index);\n      if (delimiter !== null) {\n        return { type: delimiter.type, headerStart: delimiter.nextIndex };\n      }\n    }\n\n    return null;\n  }\n\n  /**\n   * @returns {FormData}\n   */\n  parse() {\n    const initialDelimiter = this.#findInitialDelimiter();\n    if (initialDelimiter === null) {\n      throw new TypeError(\"Unable to parse body as form data\");\n    }\n\n    const formData = new FormData();\n    if (initialDelimiter.type === 2) {\n      return formData;\n    }\n\n    let headerText = \"\";\n    let headerStart = initialDelimiter.headerStart;\n    let state = 1;\n    let fileStart = 0;\n\n    for (let i = headerStart; 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 === 1) {","sourceCodeStart":515,"sourceCodeEnd":551,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/fetch/21_formdata.js#L515-L551","documentation":"Thrown by MultipartParser.parse() when #findInitialDelimiter() returns null — the parser scanned the body for the first occurrence of `--boundary` (or preamble-terminated delimiter) and found none, so there is no multipart structure to parse at all.","triggerScenarios":"`await request.formData()` where the body does not contain the `--<boundary>` delimiter: empty body, wrong boundary in body vs content-type, body sent as application/x-www-form-urlencoded but labeled multipart, or a truncated upload.","commonSituations":"Boundary mismatch after a proxy rewrites content-type; client and server disagree on the boundary constant; body read as empty because it was already consumed or the request had no body; upstream tests posting raw bytes with a multipart content type.","solutions":["Ensure the body literally starts with (or contains) `--` + the exact boundary from the content type, and parts end with `--boundary--`.","Prefer `new FormData()` + fetch so boundary and body always match.","On servers, catch TypeError from formData() and return 400 instead of crashing the handler."],"exampleFix":"// before\nconst form = await request.formData(); // throws if body lacks --boundary\n\n// after\nlet form;\ntry {\n  form = await request.formData();\n} catch {\n  return new Response(\"invalid multipart body\", { status: 400 });\n}","handlingStrategy":"try-catch","validationCode":"const ct = req.headers.get(\"content-type\") ?? \"\";\nconst m = ct.match(/boundary=(?:\"([^\"]+)\"|([^;\\s]+))/i);\nif (!m) return new Response(\"boundary missing\", { status: 400 });\n// optionally verify body actually contains `--${m[1] ?? m[2]}` before parsing","typeGuard":null,"tryCatchPattern":"try { const form = await req.formData(); } catch (e) { if (e instanceof TypeError && e.message === \"Unable to parse body as form data\") return new Response(\"invalid multipart body\", { status: 400 }); throw e; }","preventionTips":["Generate multipart bodies with a library or FormData, never by string concatenation.","Verify the boundary in content-type matches the delimiters in the body in test fixtures.","Return 400, not 500, for unparseable form bodies on public endpoints."],"tags":["form-data","multipart","parsing","fetch"],"backgroundTag":"multipart-parse-limit-exceeded","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}