From harness-claude
Shapes and composes Zod object schemas with pick, omit, partial, required, extend, merge, strict, and passthrough for update payloads, schema extensions, and key handling.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Shape and compose Zod objects with pick, omit, partial, required, extend, merge, strict, and passthrough
Defines runtime-validated TypeScript schemas using Zod's z.object, primitives, enums, literals, composition, and parsing for API responses, forms, env vars, query params.
Creates reusable Zod v4 schemas to validate API payloads, forms, and config inputs in TypeScript apps. Handles coercion, transforms, errors, and type inference for runtime type safety.
Share bugs, ideas, or general feedback.
Shape and compose Zod objects with pick, omit, partial, required, extend, merge, strict, and passthrough
id).pick() to create a schema with only specified keys:import { z } from 'zod';
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string(),
email: z.string().email(),
role: z.enum(['admin', 'viewer']),
createdAt: z.date(),
});
// Only name and email — for public-facing profile display
const PublicProfileSchema = UserSchema.pick({ name: true, email: true });
type PublicProfile = z.infer<typeof PublicProfileSchema>; // { name: string; email: string }
.omit() to exclude specific keys:// Create user input — exclude server-generated fields
const CreateUserSchema = UserSchema.omit({ id: true, createdAt: true });
type CreateUserInput = z.infer<typeof CreateUserSchema>;
// { name: string; email: string; role: 'admin' | 'viewer' }
.partial() to make all fields optional (for PATCH/update payloads):// Full partial — all fields optional
const UpdateUserSchema = UserSchema.partial();
// Selective partial — only some fields optional
const PatchUserSchema = UserSchema.partial({ name: true, role: true });
// id and email remain required
.required() to make optional fields required again:const DraftSchema = z.object({
title: z.string().optional(),
body: z.string().optional(),
publishedAt: z.date().optional(),
});
// Publish requires all fields
const PublishSchema = DraftSchema.required();
.extend() to add new fields to an existing schema:const BaseEntitySchema = z.object({
id: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
});
const PostSchema = BaseEntitySchema.extend({
title: z.string().min(1),
content: z.string(),
authorId: z.string().uuid(),
tags: z.array(z.string()).default([]),
});
.merge() to combine two object schemas (right side wins on key conflicts):const TimestampsSchema = z.object({
createdAt: z.date(),
updatedAt: z.date(),
deletedAt: z.date().nullable(),
});
const ArticleSchema = z
.object({
title: z.string(),
content: z.string(),
})
.merge(TimestampsSchema);
const StrictUserSchema = UserSchema.strict();
// Throws if input has keys not in the schema
const PassthroughSchema = UserSchema.passthrough();
// Passes unknown keys through to the output unchanged
const StrippedSchema = UserSchema.strip(); // default behavior — explicit for clarity
const keys = Object.keys(UserSchema.shape); // ['id', 'name', 'email', 'role', 'createdAt']
const emailSchema = UserSchema.shape.email; // z.ZodString
.extend() vs .merge():
Both add fields to an object schema. The difference:
.extend() accepts a plain object of Zod types (more ergonomic, handles most cases).merge() accepts a full ZodObject (needed when combining two existing schema variables)// extend — preferred when adding known fields
const WithMetadata = UserSchema.extend({ metadata: z.record(z.string()) });
// merge — preferred when combining two schema variables
const Combined = SchemaA.merge(SchemaB);
Deep partial:
Zod's .partial() is shallow — nested objects remain fully required. For deep partial, recurse manually or use a utility:
function deepPartial<T extends z.ZodRawShape>(schema: z.ZodObject<T>) {
const entries = Object.entries(schema.shape).map(([key, val]) => {
if (val instanceof z.ZodObject) {
return [key, deepPartial(val).optional()];
}
return [key, (val as z.ZodTypeAny).optional()];
});
return z.object(Object.fromEntries(entries));
}
Deriving CRUD schemas from one base:
const TaskSchema = z.object({
id: z.string().uuid(),
title: z.string().min(1),
description: z.string().optional(),
priority: z.enum(['low', 'medium', 'high']),
dueDate: z.date().optional(),
completedAt: z.date().nullable(),
});
export const CreateTaskSchema = TaskSchema.omit({ id: true, completedAt: true });
export const UpdateTaskSchema = TaskSchema.partial().required({ id: true });
export const TaskResponseSchema = TaskSchema;
export type Task = z.infer<typeof TaskSchema>;
export type CreateTaskInput = z.infer<typeof CreateTaskSchema>;
export type UpdateTaskInput = z.infer<typeof UpdateTaskSchema>;