{"record":{"id":"c8de89cfa50949e6","repo":"Hmbown/CodeWhale","slug":"expected-application-x-www-form-urlencoded","errorCode":null,"errorMessage":"expected application/x-www-form-urlencoded","messagePattern":"expected application/x-www-form-urlencoded","errorType":"http","errorClass":"FormBodyError","httpStatus":415,"severity":"error","filePath":"web/lib/bounded-form.ts","lineNumber":17,"sourceCode":"export class FormBodyError extends Error {\n  constructor(\n    readonly status: 400 | 413 | 415,\n    message: string\n  ) {\n    super(message);\n    this.name = \"FormBodyError\";\n  }\n}\n\nexport async function readBoundedUrlEncodedForm(\n  request: Request,\n  maxBytes: number\n): Promise<URLSearchParams> {\n  const mediaType = request.headers.get(\"content-type\")?.split(\";\", 1)[0]?.trim().toLowerCase();\n  if (mediaType !== \"application/x-www-form-urlencoded\") {\n    throw new FormBodyError(415, \"expected application/x-www-form-urlencoded\");\n  }\n\n  const rawLength = request.headers.get(\"content-length\");\n  if (rawLength !== null) {\n    if (!/^\\d+$/.test(rawLength)) throw new FormBodyError(400, \"invalid Content-Length\");\n    if (Number(rawLength) > maxBytes) throw new FormBodyError(413, \"payload too large\");\n  }\n\n  if (!request.body) return new URLSearchParams();\n\n  const reader = request.body.getReader();\n  const chunks: Uint8Array[] = [];\n  let total = 0;\n  while (true) {\n    const { done, value } = await reader.read();\n    if (done) break;\n    total += value.byteLength;\n    if (total > maxBytes) {","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/web/lib/bounded-form.ts#L1-L35","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nawait fetch('/submit', { method: 'POST', body: JSON.stringify({ q: 'hi' }) });\n\n// after - URLSearchParams sets application/x-www-form-urlencoded automatically\nawait fetch('/submit', { method: 'POST', body: new URLSearchParams({ q: 'hi' }) });","handlingStrategy":"validation","validationCode":"const mediaType = (request.headers.get('content-type') || '').split(';', 1)[0].trim().toLowerCase();\nif (mediaType !== 'application/x-www-form-urlencoded') {\n  return new Response('expected application/x-www-form-urlencoded', { status: 415 });\n}\nconst params = await readBoundedUrlEncodedForm(request, maxBytes);","typeGuard":null,"tryCatchPattern":"try {\n  const params = await readBoundedUrlEncodedForm(request, MAX_BYTES);\n} catch (err) {\n  if (err instanceof FormBodyError) {\n    return new Response(err.message, { status: err.status, headers: { 'content-type': 'text/plain' } });\n  }\n  throw err;\n}","preventionTips":["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"],"tags":["web","http","forms","cloudflare-workers"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}