{"record":{"id":"49fc73ba189a24d2","repo":"withastro/astro","slug":"bad-request-49fc73","errorCode":"BAD_REQUEST","errorMessage":"Failed to validate: ${JSON.stringify(issues, null, 2)}","messagePattern":"Failed to validate: (.+?)","errorType":"validation","errorClass":"ActionInputError","httpStatus":400,"severity":"error","filePath":"packages/astro/src/actions/runtime/server.ts","lineNumber":92,"sourceCode":"\nfunction getFormServerHandler<TOutput, TInputSchema extends z.$ZodType>(\n\thandler: ActionHandler<TInputSchema, TOutput>,\n\tinputSchema?: TInputSchema,\n) {\n\treturn async (unparsedInput: unknown, context: ActionAPIContext): Promise<Awaited<TOutput>> => {\n\t\tif (!(unparsedInput instanceof FormData)) {\n\t\t\tthrow new ActionError({\n\t\t\t\tcode: 'UNSUPPORTED_MEDIA_TYPE',\n\t\t\t\tmessage: 'This action only accepts FormData.',\n\t\t\t});\n\t\t}\n\n\t\tif (!inputSchema) return await handler(unparsedInput, context);\n\n\t\tconst parsed = await parseFormInput(inputSchema, unparsedInput);\n\n\t\tif (!parsed.success) {\n\t\t\tthrow new ActionInputError(parsed.error.issues);\n\t\t}\n\t\treturn await handler(parsed.data, context);\n\t};\n}\n\nasync function parseFormInput(inputSchema: z.$ZodType, unparsedInput: FormData) {\n\tconst baseSchema = unwrapBaseZ4ObjectSchema(inputSchema, unparsedInput);\n\tconst input =\n\t\tbaseSchema instanceof z.$ZodObject\n\t\t\t? formDataToObject(unparsedInput, baseSchema)\n\t\t\t: unparsedInput;\n\n\tconst parsed = await z.safeParseAsync(inputSchema, input);\n\treturn parsed;\n}\n\nfunction getJsonServerHandler<TOutput, TInputSchema extends z.$ZodType>(\n\thandler: ActionHandler<TInputSchema, TOutput>,","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/withastro/astro/blob/52e6c34790cc8ac4e69e6135ace06049867e5c4a/packages/astro/src/actions/runtime/server.ts#L74-L110","documentation":"For accept:'form' actions with an input schema, Astro converts the FormData to an object (via formDataToObject) and validates it with zod. When validation fails, it throws an input error (code BAD_REQUEST) whose message embeds the full zod issues array as JSON, so every failing field and reason is visible in the error text.","triggerScenarios":"A required form field missing, a value failing its constraint (email format, min/max), or — most commonly — a numeric/boolean field sent as a string because raw form values are always strings and the schema lacks coercion (z.coerce.number()).","commonSituations":"HTML forms omitting optional-but-schema-required fields; forgetting z.coerce on number/boolean/date fields in form actions; whitespace or empty strings from unfilled inputs failing string constraints; client and server schemas drifting apart.","solutions":["Read the JSON issues array in the error message: each issue names the failing path and message — fix those fields first","Add coercion for form fields: z.coerce.number(), z.coerce.boolean(), z.coerce.date() instead of plain z.number()","On the caller side use the safe pattern (await actions.x(fd) then isInputError(result)) to render field errors instead of throwing","Make optional fields optional in the schema (.optional()) when the form may omit them"],"exampleFix":"// before — schema assumes JSON types\nconst input = z.object({ qty: z.number().min(1) });\n// form sends qty='3' (string) -> Failed to validate\n\n// after\nconst input = z.object({ qty: z.coerce.number().min(1) });","handlingStrategy":"validation","validationCode":"// validate against the shared schema before submitting the form\nimport { subscribeSchema } from './schema';\nconst parsed = subscribeSchema.safeParse(Object.fromEntries(new FormData(formEl)));\nif (!parsed.success) {\n  renderFieldErrors(parsed.error.issues);\n  return;\n}\nawait actions.subscribe(new FormData(formEl));","typeGuard":"import { isInputError } from 'astro:actions';\n// after a safe (non-orThrow) call:\nconst result = await actions.subscribe(fd);\nif (isInputError(result)) {\n  // result.error.issues: [{ path: ['email'], message: '...' }]\n}","tryCatchPattern":"try {\n  await actions.subscribe.orThrow(fd);\n} catch (e) {\n  if (e instanceof ActionError && e.code === 'BAD_REQUEST' && e.message.includes('Failed to validate')) {\n    // parse the embedded issues JSON and show field errors\n  } else throw e;\n}","preventionTips":["Always use z.coerce.* for number/boolean/date fields on accept:'form' actions","Mark fields the form may omit as .optional() with defaults","Validate client-side with the same shared schema for instant feedback"],"tags":["astro-actions","zod","form-validation","bad-request","schema"],"backgroundTag":"schema-validation-failed","analyzedSha":"52e6c34790cc8ac4e69e6135ace06049867e5c4a","analyzedAt":"2026-08-18T18:48:03.901Z","contentChangedAt":"2026-08-18T18:48:03.901Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}