install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/zod-infer-types" ~/.claude/skills/intense-visions-harness-engineering-zod-infer-types && rm -rf "$T"
manifest:
agents/skills/claude-code/zod-infer-types/SKILL.mdsource content
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
and you need to access both the input and output types separately.transform() - 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
to derive the TypeScript type from a schema — always export both together:z.infer<typeof Schema>
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
, distinguish between input and output types:.transform()
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
as an explicit alias forz.output<typeof Schema>
— communicates intent clearly:z.infer
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
when you want to accept a schema that must produce a known output type:z.ZodSchema<T>
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.