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

In packageBytes, .formData() only decodes two content types: multipart/form-data and application/x-www-form-urlencoded. If a Content-Type is present but its essence is anything else (text/plain, application/json, ...), parsing stops with this TypeError - the body will not be reinterpreted as form data.

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 89f33cbef2)

Solutions

  1. Branch on the Content-Type: JSON.parse(await req.text()) for application/json, formData() only for the two form types
  2. Fix the client to send application/x-www-form-urlencoded or multipart/form-data
  3. Return 415 Unsupported Media Type when the type is not one your handler supports

Example fix

// before
const form = await req.formData(); // client sent application/json -> throws

// after
const ct = req.headers.get("content-type") ?? "";
if (ct.includes("application/json")) {
  const data = await req.json(); /* ... */
} else if (ct.includes("form-data") || ct.includes("urlencoded")) {
  const form = await req.formData(); /* ... */
} else {
  return new Response("unsupported media type", { status: 415 });
}
Defensive patterns

Strategy: validation

Validate before calling

const ct = req.headers.get("content-type") ?? "";
if (!/multipart\/form-data|application\/x-www-form-urlencoded/i.test(ct)) {
  return new Response("expected form data", { status: 415 });
}
const form = await req.formData();

Type guard

function isFormDataContentType(ct: string | null): boolean {
  if (ct == null) return false;
  const essence = ct.split(";")[0].trim().toLowerCase();
  return essence === "multipart/form-data" || essence === "application/x-www-form-urlencoded";
}

Try / catch

try { return await req.formData(); } catch (e) {
  if (e instanceof TypeError && e.message.includes("can not be decoded as form data")) {
    return new Response("send multipart/form-data or x-www-form-urlencoded", { status: 415 });
  }
  throw e;
}

Prevention

When it happens

Trigger: await req.formData() on a request posted with Content-Type: text/plain or application/json; senders that default to text/plain when no explicit type is set (older fetch or curl -d without --data-urlencode).

Common situations: Accepting form posts while the client library actually sends JSON; curl-based integrations that forget the content-type flag; endpoints that multiplex JSON and form data but always call formData().

Related errors


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