Hmbown/CodeWhale · error · FormBodyError

expected application/x-www-form-urlencoded

Error message

expected application/x-www-form-urlencoded

What it means

readBoundedUrlEncodedForm only accepts bodies whose Content-Type media type (before parameters) is exactly application/x-www-form-urlencoded. Anything else throws FormBodyError with status 415 before reading the body.

Solutions

  1. Send the body as application/x-www-form-urlencoded: `curl -d 'token=...'` or set the header explicitly.
  2. For JSON or multipart payloads, use a route/helper that accepts that media type instead.
  3. If using fetch, set `Content-Type: application/x-www-form-urlencoded` and encode the body with URLSearchParams.

Example fix

// before
await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) });
// after
await fetch(url, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(data) });
Defensive patterns

Strategy: validation

Validate before calling

const mt = request.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase();
if (mt !== "application/x-www-form-urlencoded") throw new Error(`wrong media type: ${mt}`);

Try / catch

try {
  const form = await readBoundedUrlEncodedForm(request, MAX);
} catch (err) {
  if (err instanceof FormBodyError && err.status === 415) {
    return new Response("send application/x-www-form-urlencoded", { status: 415 });
  }
  throw err;
}

Prevention

When it happens

Trigger: POST/form calls to a route using this helper while sending content-type application/json, multipart/form-data, text/plain, or a missing content-type header.

Common situations: A frontend switching from form submits to fetch() with a JSON body; an API client posting multipart data; scripts posting with curl without an explicit content type matching the form encoding.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/c8de89cfa50949e6. Report an issue: GitHub.

Appendix: source

Thrown at web/lib/bounded-form.ts:19

import { BodyReadError, readBoundedBody } from "./bounded-body";

export class FormBodyError extends Error {
  constructor(
    readonly status: 400 | 413 | 415,
    message: string
  ) {
    super(message);
    this.name = "FormBodyError";
  }
}

export async function readBoundedUrlEncodedForm(
  request: Request,
  maxBytes: number
): Promise<URLSearchParams> {
  const mediaType = request.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
  if (mediaType !== "application/x-www-form-urlencoded") {
    throw new FormBodyError(415, "expected application/x-www-form-urlencoded");
  }

  try {
    const bytes = await readBoundedBody(request, maxBytes);
    return new URLSearchParams(new TextDecoder().decode(bytes));
  } catch (cause) {
    if (cause instanceof BodyReadError) throw new FormBodyError(cause.status, cause.message);
    throw cause;
  }
}

View on GitHub (pinned to 433685b202)