Zod Infer Types
Derive TypeScript types from Zod schemas with z.infer, input vs output types, and ZodTypeAny
When to Use
- You want TypeScript types to be automatically derived from Zod schemas (single source of truth)
- A schema uses
.transform()and you need to access both the input and output types separately - You are writing utilities that accept any Zod schema and need to express that generically
- You want to avoid maintaining separate TypeScript interfaces alongside Zod schemas
Instructions
- Use
z.infer<typeof Schema>to derive the TypeScript type from a schema — always export both together:
import { z } from 'zod';
export const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1),
email: z.string().email(),
role: z.enum(['admin', 'editor', 'viewer']),
createdAt: z.date(),
});
export type User = z.infer<typeof UserSchema>;
// {
// id: string;
// name: string;
// email: string;
// role: 'admin' | 'editor' | 'viewer';
// createdAt: Date;
// }
- When a schema uses
.transform(), distinguish between input and output types:
const DateInputSchema = z.object({
name: z.string(),
createdAt: z
.string()
.datetime()
.transform((s) => new Date(s)),
});
type DateInput = z.input<typeof DateInputSchema>;
// { name: string; createdAt: string } ← what you pass to .parse()
type DateOutput = z.infer<typeof DateInputSchema>;
// { name: string; createdAt: Date } ← what you get back from .parse()
// z.infer always gives you the OUTPUT type
// z.input gives you the INPUT type (before transforms)
- Use
z.output<typeof Schema>as an explicit alias forz.infer— communicates intent clearly:
type ParsedUser = z.output<typeof UserSchema>; // same as z.infer<typeof UserSchema>
type RawUser = z.input<typeof UserSchema>; // before any transforms
- Write generic functions that accept any Zod schema using
z.ZodTypeAny:
import { z } from 'zod';
function parseOrThrow<T extends z.ZodTypeAny>(schema: T, data: unknown): z.output<T> {
return schema.parse(data);
}
function safeValidate<T extends z.ZodTypeAny>(
schema: T,
data: unknown
): { success: true; data: z.output<T> } | { success: false; error: z.ZodError } {
const result = schema.safeParse(data);
if (result.success) return { success: true, data: result.data };
return { success: false, error: result.error };
}
- Use
z.ZodSchema<T>when you want to accept a schema that must produce a known output type:
function registerSchema<T>(name: string, schema: z.ZodSchema<T>): void {
// schema.parse(data) is guaranteed to return T
schemaRegistry.set(name, schema);
}
// TypeScript enforces the schema output matches T
registerSchema<User>('user', UserSchema); // OK
registerSchema<User>('user', z.object({ foo: z.string() })); // Type error
- Extract element types from array schemas:
const ItemsSchema = z.array(
z.object({
id: z.string(),
label: z.string(),
value: z.number(),
})
);
type Items = z.infer<typeof ItemsSchema>; // { id: string; label: string; value: number }[]
type Item = Items[number]; // { id: string; label: string; value: number }
// Or: type Item = z.infer<typeof ItemsSchema.element>
- Extract enum values as a type and as a runtime value array:
const RoleSchema = z.enum(['admin', 'editor', 'viewer']);
type Role = z.infer<typeof RoleSchema>; // 'admin' | 'editor' | 'viewer'
const roles = RoleSchema.options; // ['admin', 'editor', 'viewer'] — runtime array
Details
Never write a TypeScript type alongside a Zod schema that describes the same shape:
// Bad: duplicate maintenance burden
interface User {
id: string;
name: string;
email: string;
}
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
});
// Good: one source of truth
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
});
type User = z.infer<typeof UserSchema>;
Conditional types with discriminated unions:
const EventSchema = z.discriminatedUnion('type', [
z.object({ type: z.literal('click'), x: z.number(), y: z.number() }),
z.object({ type: z.literal('keypress'), key: z.string() }),
]);
type Event = z.infer<typeof EventSchema>;
// { type: 'click'; x: number; y: number } | { type: 'keypress'; key: string }
// Extract individual variants
type ClickEvent = Extract<Event, { type: 'click' }>;
Utility type helpers for Zod:
// Make all fields required (undo partials)
type RequiredUser = Required<z.infer<typeof UserSchema>>;
// Pick specific fields
type UserPreview = Pick<User, 'id' | 'name'>;
// These work because z.infer produces a plain TypeScript type
Source
https://zod.dev/api#type-inference
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.