colinhacks/zod · error · Error
Invalid element at key "${k}": expected a Zod schema
Error message
Invalid element at key "${k}": expected a Zod schema What it means
`normalizeDef` (schemas.ts:1838) walks every key of an object's shape and asserts each value has the `$ZodType` trait (`def.shape[k]._zod.traits.has("$ZodType")`). If any value is a plain object, primitive, class, or non-Zod schema, it throws `Invalid element at key "${k}"` at construction time. This catches mistakes where a field is given a raw value instead of a Zod schema.
Source
Thrown at packages/zod/src/v4/core/schemas.ts:1842
propValues: util.PropValues;
output: $InferObjectOutput<Shape, Config["out"]>;
input: $InferObjectInput<Shape, Config["in"]>;
optin?: "optional" | undefined;
optout?: "optional" | undefined;
}
export type $ZodLooseShape = Record<string, any>;
export interface $ZodObject<
/** @ts-ignore Cast variance */
out Shape extends Readonly<$ZodShape> = Readonly<$ZodShape>,
out Params extends $ZodObjectConfig = $ZodObjectConfig,
> extends $ZodType<any, any, $ZodObjectInternals<Shape, Params>> {}
function normalizeDef(def: $ZodObjectDef) {
const keys = Object.keys(def.shape);
for (const k of keys) {
if (!def.shape?.[k]?._zod?.traits?.has("$ZodType")) {
throw new Error(`Invalid element at key "${k}": expected a Zod schema`);
}
}
const okeys = util.optionalKeys(def.shape);
return {
...def,
keys,
keySet: new Set(keys),
numKeys: keys.length,
optionalKeys: new Set(okeys),
};
}
function handleCatchall(
proms: Promise<any>[],
input: any,
payload: ParsePayload,
ctx: ParseContext,View on GitHub (pinned to 912f0f51b0)
Solutions
- Wrap every field value in a Zod schema: `z.object({ foo: z.string() })`.
- For nested data use `z.object()` recursively rather than a plain object.
- Check the offending key named in the error message and ensure its value is a Zod schema instance.
Example fix
// before
const S = z.object({ name: "string", count: 0 }); // throws at 'name'
// after
const S = z.object({ name: z.string(), count: z.number() }); Defensive patterns
Strategy: validation
Validate before calling
import { z } from "zod";
function isZodSchema(v: unknown): v is z.ZodType {
return !!v && typeof v === "object" && !!(v as any)?._zod?.traits?.has("$ZodType");
}
function assertObjectShape(shape: Record<string, unknown>) {
for (const [k, v] of Object.entries(shape)) {
if (!isZodSchema(v)) {
throw new Error(`Invalid element at key "${k}": expected a Zod schema`);
}
}
} Type guard
import { z } from "zod";
function isZodSchema(v: unknown): v is z.ZodType {
return !!v && typeof v === "object" && !!(v as any)?._zod?.traits?.has("$ZodType");
}
// Usage: Object.values(shape).every(isZodSchema) Prevention
- Always wrap field values with a `z.*()` constructor; never pass raw primitives or plain objects.
- For nested data use `z.object()` recursively, not a literal `{}`.
- If a field looks like a schema but throws, check its import path (a bad import resolves to `undefined`).
When it happens
Trigger: Constructing `z.object({ foo: "string" })`, `z.object({ count: 0 })`, `z.object({ nested: { x: z.string() } })` (raw object instead of `z.object()`), or passing a Yup/Joi schema. Any value lacking `_zod.traits` triggers it.
Common situations: Forgetting the `z.` prefix (writing `string` instead of `z.string()`); nesting raw config objects; copy-pasting TypeScript types into runtime schemas; importing a schema that is actually `undefined` due to a bad import path.
Related errors
- Key ${value} not found in enum
- Invalid UUID version: "${def.version}"
- .pick() cannot be used on object schemas containing refineme
AI-assisted analysis of colinhacks/zod@912f0f51b0 (2026-08-03).
Data as JSON: /data/errors/4197025d4bd6c1a4.json.
Report an issue: GitHub.