From harness-claude
Derives TypeScript types from Zod schemas using z.infer for outputs, z.input/z.output for transforms, and ZodTypeAny/ZodSchema for generics. Use for single-source schemas and utilities.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Derive TypeScript types from Zod schemas with z.infer, input vs output types, and ZodTypeAny
Uses Zod schemas for runtime validation of APIs, forms, env vars, and external data while inferring TypeScript types as single source of truth.
Provides Zod best practices for schema validation, type safety, parsing with safeParse, z.infer, and error handling in TypeScript apps. Use for z.object schemas, z.string validations.
Share bugs, ideas, or general feedback.
Derive TypeScript types from Zod schemas with z.infer, input vs output types, and ZodTypeAny
.transform() and you need to access both the input and output types separatelyz.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;
// }
.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)
z.output<typeof Schema> as an explicit alias for z.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
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 };
}
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
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>
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
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
https://zod.dev/api#type-inference