Harness-engineering zod-infer-types

Zod Infer Types

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.md
source 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
    .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

  1. 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;
// }
  1. 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)
  1. Use
    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
  1. 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 };
}
  1. 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
  1. 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>
  1. 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

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. 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.