ZodError: [ { "code": "invalid_type", "expected": "string", "received": "undefined", "path": [ "email" ], "message": "Required" } ]
Node.js · updated 2026-11-03
Thrown by schema.parse() when the data does not match the schema. The error is an array of issues, each with path (which field), code (what kind of failure), expected and received. A 500 here usually means untrusted input was parsed with parse() instead of safeParse().
ZodError: [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [ "email" ],
"message": "Required"
}
]
at get error (file:///app/node_modules/zod/lib/types.js:55:31)
at ZodObject.parse (file:///app/node_modules/zod/lib/types.js:160:22)
at createUser (file:///app/src/routes/users.js:14:31)Quick fix
Read path and message in each issue: they name the field and the problem. For request bodies and other outside input, use safeParse() and return a 400 with error.issues (or error.flatten()) so a bad request is the client's error, not a crash. If the input is your own data, fix the producer or the schema.
- Look at path to find the field and code to see the kind of failure (invalid_type, too_small, invalid_string).
- For HTTP input, switch to safeParse() and answer 400 with the issues.
- For environment variables or config, keep parse() at startup so a bad value stops the boot with a clear message.
- If the shape changed on purpose, update the schema; use .optional() or .default() for fields that can be missing.
- Use z.coerce.number() for numbers that arrive as strings (query strings, form data).
import { z } from 'zod';
const NewUser = z.object({
email: z.string().email(),
age: z.coerce.number().int().min(13).optional(),
});
app.post('/users', (req, res) => {
const result = NewUser.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: 'invalid_body', issues: result.error.flatten().fieldErrors });
}
res.json(createUser(result.data));
});Why it happens
parse() on untrusted input
Request bodies, query strings and webhook payloads come from outside. parse() throws on the first bad request and turns a client mistake into a 500.
A missing or renamed field
A client sending userEmail instead of email, or an API that dropped a field, gives invalid_type with received: undefined.
Numbers and booleans as strings
Query parameters and form fields are always strings, so z.number() rejects "42" unless you use z.coerce.number().
Schema and data drifting apart
A database column that became nullable, or an upstream API version change, makes parsing your own data fail after a deploy.
Other fixes
- Log error.issues, not error.message, so the error tracker groups by field and code.
- Share schemas between client and server so both sides agree on the shape.
Same error, other wording
- ZodError: [ { "code": "too_small", "minimum": 1, "type": "string", "path": [ "name" ], "message": "String must contain at least 1 character(s)" } ]
- ZodError: [ { "code": "invalid_string", "validation": "email", "path": [ "email" ], "message": "Invalid email" } ]
- ZodError: [ { "expected": "number", "code": "invalid_type", "path": [ "page" ], "message": "Expected number, received string" } ]
Let your coding agent catch the next one
Runheed records this error in production with the stack trace, the breadcrumbs before it and the agent session that shipped the code, and serves it to Claude Code, Cursor or any MCP client. With it connected, you can ask:
> Find the ZodError exceptions in production, group them by path and code, tell me which come from client input (should be 400s) and which from our own data, and switch the input ones to safeParse.
Related errors
- TypeError: Cannot destructure property 'id' of 'undefined' as it is undefined.
- TypeError: Cannot read properties of undefined (reading 'x')
- SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
References: Zod docs: formatting errors, Zod docs: basic usage (parse and safeParse)