denoland/deno · error · TypeError

Body can not be decoded as form data

Error message

Body can not be decoded as form data

What it means

Thrown in packageBytes() when converting bytes to FormData and the body has a Content-Type, but its essence is neither multipart/form-data nor application/x-www-form-urlencoded. Only those two MIME types are decodable into FormData, so anything else (e.g. text/plain, application/json, multipart/mixed) is rejected.

Solutions

  1. Branch on content type before parsing: use json() for application/json, formData() only for the two form types.
  2. When a form endpoint returns an error response, read it as text/json according to its content type.
  3. On the sending side, ensure uploads use multipart/form-data or application/x-www-form-urlencoded.

Example fix

// before
const form = await res.formData(); // res is application/json error

// after
const form = res.headers.get("content-type")?.includes("multipart/form-data") ||
  res.headers.get("content-type")?.includes("application/x-www-form-urlencoded")
  ? await res.formData()
  : await res.json();
Defensive patterns

Strategy: validation

Validate before calling

function formParseable(res) {
  const ct = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
  return ct === "multipart/form-data" || ct === "application/x-www-form-urlencoded";
}
const data = formParseable(res) ? await res.formData() : await res.json();

Try / catch

try { await res.formData(); } catch (e) { if (e instanceof TypeError && e.message === "Body can not be decoded as form data") { /* fall back to text()/json() based on content-type */ } else throw e; }

Prevention

When it happens

Trigger: `await response.formData()` with `Content-Type: text/plain`, `application/json`, `multipart/mixed`, or `application/octet-stream`. Any non-empty, non-whitespace MIME essence that is not one of the two supported types hits this throw.

Common situations: Calling formData() unconditionally on API responses without checking content type; servers that answer form endpoints with JSON errors (400 application/json) — the formData() call then throws instead of surfacing the error payload.

Related errors


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

Appendix: source

Thrown at ext/fetch/22_body.js:470

        if (essence === "multipart/form-data") {
          const boundary = mimeType.parameters.get("boundary");
          if (boundary === null) {
            throw new TypeError(
              "Cannot turn into form data: missing boundary parameter in mime type of multipart form data",
            );
          }
          return parseFormData(chunkToU8(bytes), boundary);
        } else if (essence === "application/x-www-form-urlencoded") {
          // TODO(@AaronO): pass as-is with StringOrBuffer in op-layer
          const entries = parseUrlEncoded(chunkToU8(bytes));
          return formDataFromEntries(
            ArrayPrototypeMap(
              entries,
              (x) => ({ name: x[0], value: x[1] }),
            ),
          );
        }
        throw new TypeError("Body can not be decoded as form data");
      }
      throw new TypeError("Missing content type");
    }
    case "JSON":
      return JSONParse(chunkToString(bytes));
    case "text":
      return chunkToString(bytes);
  }
}

/**
 * @param {BodyInit} object
 * @returns {{body: InnerBody, contentType: string | null}}
 */
function extractBody(object) {
  /** @type {ReadableStream<Uint8Array> | { body: Uint8Array | string, consumed: boolean }} */
  let stream;
  let source = null;

View on GitHub (pinned to 9ad36f7a2c)