Fixing tRPC Zod Input Validation Errors
Learn how to diagnose and fix HTTP 400 BAD_REQUEST errors in tRPC caused by Zod schema mismatches between the client payload and server expectations.
21 Mar 2026, 00:20 UTC

The Problem: Runtime Validation Failures
You have defined a tRPC procedure with a Zod schema for input validation, but your client requests are returning a BAD_REQUEST (HTTP 400) error. This happens when the data sent from the client does not strictly match the schema defined on the server, even if your TypeScript types appear correct in the IDE.
The core takeaway: TypeScript provides compile-time safety, but Zod provides runtime enforcement. If the JSON payload arriving at the server violates the Zod schema, tRPC will reject the request before it ever reaches your resolver logic.
Diagnostic Matrix: Common Mismatches
| Symptom | Likely Cause | Zod Schema Indicator |
|---|---|---|
| Unexpected 400 on numeric fields | Sending a string (e.g., from a URL param) instead of a number | z.number() |
| Missing field error | Client omitted a field that is not marked optional | z.string() (without .optional()) |
| Invalid object shape | Nested object structure mismatch or extra unexpected keys | z.object({ ... }) |
| Date parsing failure | Sending an ISO string to a Zod Date object | z.date() |
Step-by-Step Diagnostic Process
1. Inspect the Network Payload
Before changing code, verify exactly what the browser is sending. Open your browser's Developer Tools (F12), go to the Network tab, and trigger the tRPC call.
- Locate the request (usually a
GETorPOSTto/api/trpc/[procedureName]). - Check the Payload tab to see the raw JSON being sent.
- Check the Response tab. tRPC typically returns a JSON object containing a
messageand acode(e.g.,BAD_REQUEST) that specifies which Zod field failed validation.
2. Compare Payload to Server Schema
Locate the procedure definition in your server-side router. Compare the raw JSON from the network tab against the .input() definition.
// Server-side definition
export const appRouter = router({
updateUser: publicProcedure
.input(z.object({
id: z.number(),
email: z.string().email(),
age: z.number().optional(),
}))
.mutation(({ input }) => {
// logic
}),
});
If the network payload shows { \"id\": \"123\", \"email\": \"test@example.com\" }, the request will fail because id is a string, but the schema requires a z.number().
3. Verify Client-Side Type Casting
If you are using any or as any on the client side to bypass TypeScript errors, you are disabling the primary benefit of tRPC. This allows malformed data to reach the server, triggering the runtime Zod error.
Fixes Based on Findings
Scenario A: Data is coming from an HTML Input
HTML input values are always strings. If your schema expects a number, you must cast the value before passing it to the tRPC client.
// Run this on the client side
const userId = parseInt(inputValue, 10);
await trpc.updateUser.mutate({ id: userId, email: 'user@example.com' });
Scenario B: Handling Optional Fields
If a field is not always available, ensure the Zod schema explicitly allows it. By default, all keys in z.object() are required.
// Change this:
age: z.number()
// To this:
age: z.number().optional()
Scenario C: Coercing Types
If you want Zod to attempt to convert the input (e.g., converting the string \"123\" to the number 123 automatically), use z.coerce.
// Server-side
id: z.coerce.number() // Automatically converts strings to numbers if possible
Limitations and Risks
- z.passthrough(): Avoid using
.passthrough()on your schemas during debugging. It allows unknown keys to enter the resolver, which can mask bugs where the client is sending the wrong object structure entirely. - Version Mismatches: Ensure Zod versions are consistent between the server and any shared packages to avoid discrepancies in how
z.date()orz.enum()are handled.
Verification of Fix
To confirm the resolution:
- Clear the browser cache or refresh the page to ensure the latest client-side types are in use.
- Trigger the procedure.
- Verify the Network tab shows a
200 OKresponse and the server resolver logs the expected input types.
Rollback Procedure
If a schema change causes downstream failures in your database or business logic:
- Revert the
.input()definition in the router to the previous Zod schema version. - Redeploy the server to restore the previous validation constraints.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.