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
- Send the body as application/x-www-form-urlencoded: `curl -d 'token=...'` or set the header explicitly.
- For JSON or multipart payloads, use a route/helper that accepts that media type instead.
- 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
- Use `new URLSearchParams(obj)` as the fetch body — it sets the correct content type automatically.
- Never override the Content-Type header when submitting URL-encoded forms.
- Match the client encoding to the endpoint: JSON/multipart go to different routes.
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
- header name cannot be empty
- header name cannot be empty
- invalid Content-Length
- MCP HTTP requires an http:// or https:// URL with a host
- payload too large
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)