colinhacks/zod · error · Error
Invalid element at key
Error message
Invalid element at key "${k}": expected a Zod schema What it means
Thrown by normalizeDef() while initializing a $ZodObject whenever a value in def.shape fails the check `def.shape[k]?._zod?.traits?.has("$ZodType")`. Every property of an object's shape must itself be a Zod schema instance carrying the $ZodType trait; passing a plain value, class, string, or non-schema object is rejected at construction time so that the object's parser can safely call _zod.run on each field.
Solutions
- Wrap every field value in a Zod schema (z.string(), z.number(), z.unknown(), etc.).
- If building the shape dynamically, guard each entry: if (!value?._zod?.traits?.has("$ZodType")) shape[k] = z.unknown();
- Check for undefined caused by circular imports — reorder imports or use z.lazy().
- Run a typecheck (tsc); the object() overload signature will usually flag mistyped fields before runtime.
Example fix
// before
const Schema = z.object({ name: "string", age: Number });
// after
const Schema = z.object({ name: z.string(), age: z.number() }); Defensive patterns
Strategy: type-guard
Validate before calling
function buildObject(shape: Record<string, unknown>) {
const safe: Record<string, z.ZodType> = {};
for (const [k, v] of Object.entries(shape)) {
safe[k] = isZodSchema(v) ? v : z.unknown();
}
return z.object(safe);
} Type guard
function isZodSchema(v: unknown): v is z.ZodType {
return !!v && typeof v === "object" && (v as any)?._zod?.traits?.has("$ZodType") === true;
} Try / catch
try {
return z.object(shape as Record<string, z.ZodType>);
} catch (e) {
throw new Error(`Object shape invalid: ${(e as Error).message}. Ensure every field is a Zod schema.`);
} Prevention
- Always wrap field values with z.* factories.
- For dynamic shapes, type the input as Record<string, z.ZodType> so tsc catches mistakes.
- Watch for undefined from circular imports — use z.lazy() if needed.
When it happens
Trigger: Building z.object({ foo: "string" }), z.object({ foo: String }), z.object({ foo: { min: 1 } }), z.object({ foo: null }), or programmatically spreading a non-schema map into the shape. Also occurs when a spread/merge utility forgets to wrap a field, or when a lazy import resolves to undefined and is used as a field.
Common situations: Typing field values as native constructors (String, Number) instead of z.string()/z.number(); passing raw regex or plain option objects; circular imports that yield undefined at module-eval time; dynamic shape construction from external config without wrapping values in z.unknown() or similar.
Related errors
- implement() must be called with a function
- implementAsync() must be called with a function
- A discriminator value for key
- Can't use "invalid_type_error" or "required_error" in…
- Cannot create literal schema with no valid values
AI-assisted analysis of colinhacks/zod@2d90846af9 (2026-08-11).
Data as JSON: /api/errors/4197025d4bd6c1a4.
Report an issue: GitHub.
Appendix: source
Thrown at packages/zod/src/v4/core/schemas.ts:1853
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 2d90846af9)