Hmbown/CodeWhale · error · FormBodyError
expected application/x-www-form-urlencoded
Error message
expected application/x-www-form-urlencoded
What it means
FormBodyError with HTTP 415, thrown by readBoundedUrlEncodedForm() when the request's media type (content-type before any ';' parameters, lowercased) is not exactly application/x-www-form-urlencoded. The helper accepts only that encoding so it can enforce its byte limit while buffering.
Source
Thrown at web/lib/bounded-form.ts:17
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");
}
const rawLength = request.headers.get("content-length");
if (rawLength !== null) {
if (!/^\d+$/.test(rawLength)) throw new FormBodyError(400, "invalid Content-Length");
if (Number(rawLength) > maxBytes) throw new FormBodyError(413, "payload too large");
}
if (!request.body) return new URLSearchParams();
const reader = request.body.getReader();
const chunks: Uint8Array[] = [];
let total = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
total += value.byteLength;
if (total > maxBytes) {View on GitHub (pinned to 8880682c63)
Solutions
- Send the body as URLSearchParams (it sets the header automatically) or as urlencoded text with content-type: application/x-www-form-urlencoded
- If the request must be JSON or multipart, switch the route to a reader for that encoding
- Remove content-type overrides added by HTTP interceptors
Example fix
// before
await fetch('/submit', { method: 'POST', body: JSON.stringify({ q: 'hi' }) });
// after - URLSearchParams sets application/x-www-form-urlencoded automatically
await fetch('/submit', { method: 'POST', body: new URLSearchParams({ q: 'hi' }) }); Defensive patterns
Strategy: validation
Validate before calling
const mediaType = (request.headers.get('content-type') || '').split(';', 1)[0].trim().toLowerCase();
if (mediaType !== 'application/x-www-form-urlencoded') {
return new Response('expected application/x-www-form-urlencoded', { status: 415 });
}
const params = await readBoundedUrlEncodedForm(request, maxBytes); Try / catch
try {
const params = await readBoundedUrlEncodedForm(request, MAX_BYTES);
} catch (err) {
if (err instanceof FormBodyError) {
return new Response(err.message, { status: err.status, headers: { 'content-type': 'text/plain' } });
}
throw err;
} Prevention
- Prefer new URLSearchParams(...) as the fetch body - it sets the right content-type
- Document the accepted encoding on every form route
- Add integration tests covering wrong content-type, oversized bodies, and empty bodies
When it happens
Trigger: POSTing JSON to a route that reads its body with readBoundedUrlEncodedForm(); an HTML form with enctype="multipart/form-data"; a fetch whose body is FormData (browser sends multipart) or a plain string (text/plain).
Common situations: A front-end migrates a form to fetch with a JSON body but the Workers route still expects urlencoded; curl --json or an explicit application/json header against a form route; file-upload forms reusing a urlencoded-only endpoint.
Related errors
- payload too large
- invalid Content-Length
- ${detail || ("HTTP " + res.status)}
- ${response.status} ${response.statusText}
- Runtime API capability '${options.capability}' is not availa
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/c8de89cfa50949e6.
Report an issue: GitHub.