ACTIVATE when creating Zod schemas, inferring types, composing schemas, or integrating Zod with NestJS validation. ACTIVATE for 'Zod', 'z.object', 'z.infer', 'ZodValidationPipe', 'schema validation'. Covers: FooSchema naming convention, packages/shared location, schema composition (extend/pick/omit/merge), z.coerce for HTTP values, NestJS ZodValidationPipe integration, error formatting with safeParse+flatten. DO NOT use for: general TypeScript types (see ts-conventions), domain validation logic.
From toolingnpx claudepluginhub fabiensalles/claude-marketplace --plugin toolingThis skill uses the workspace's default tool permissions.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Details PluginEval's skill quality evaluation: 3 layers (static, LLM judge), 10 dimensions, rubrics, formulas, anti-patterns, badges. Use to interpret scores, improve triggering, calibrate thresholds.
FooSchema for schema, Foo for inferred type:
import { z } from 'zod';
export const CreateReceiptSchema = z.object({
leaseId: z.string().uuid(),
period: z.object({
year: z.number().int().min(2020),
month: z.number().int().min(1).max(12),
}),
});
export type CreateReceiptDto = z.infer<typeof CreateReceiptSchema>;
All schemas live in packages/shared so both apps/api and apps/web can import them:
packages/shared/src/
├── dto/
│ ├── receipt.dto.ts # CreateReceiptSchema, UpdateReceiptSchema
│ ├── tenant.dto.ts # CreateTenantSchema, LoginSchema
│ └── index.ts # Re-exports
├── enums/
│ └── status.ts # as const enums
└── index.ts # Barrel export
// Usage in apps/api or apps/web
import { CreateReceiptSchema, type CreateReceiptDto } from '@quittanceme/shared';
const BaseTenantSchema = z.object({
email: z.string().email(),
firstName: z.string().min(1),
lastName: z.string().min(1),
});
// Extend
const CreateTenantSchema = BaseTenantSchema.extend({
password: z.string().min(8),
});
// Pick
const LoginSchema = BaseTenantSchema.pick({ email: true }).extend({
password: z.string(),
});
// Omit
const PublicTenantSchema = BaseTenantSchema.omit({ email: true });
const WithTimestamps = z.object({
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
});
const TenantResponseSchema = BaseTenantSchema.merge(WithTimestamps);
Use z.coerce.* for values from HTTP (query strings, form data) that arrive as strings:
// ❌ AVOID - Fails on "123" from query string
const schema = z.object({
page: z.number(), // Rejects "123" (string)
});
// ✅ CORRECT - Coerces string to number
const schema = z.object({
page: z.coerce.number().int().min(1).default(1),
date: z.coerce.date(),
active: z.coerce.boolean(),
});
// src/infrastructure/http/pipes/zod-validation.pipe.ts
import { PipeTransform, BadRequestException } from '@nestjs/common';
import { ZodSchema } from 'zod';
export class ZodValidationPipe implements PipeTransform {
constructor(private readonly schema: ZodSchema) {}
transform(value: unknown) {
const result = this.schema.safeParse(value);
if (!result.success) {
throw new BadRequestException(result.error.flatten());
}
return result.data;
}
}
@Post()
async create(
@Body(new ZodValidationPipe(CreateReceiptSchema)) dto: CreateReceiptDto,
) {
return this.receiptService.generate(dto);
}
@Get()
async list(
@Query(new ZodValidationPipe(PaginationSchema)) query: PaginationDto,
) {
return this.receiptService.findAll(query);
}
safeParse + flatten() produces a clean error structure:
const result = schema.safeParse(input);
if (!result.success) {
const errors = result.error.flatten();
// {
// formErrors: [],
// fieldErrors: {
// email: ['Invalid email'],
// period: ['Required'],
// }
// }
}
| Rule | Convention |
|---|---|
| Naming | FooSchema + z.infer<typeof FooSchema> |
| Location | packages/shared/src/dto/ |
| Composition | .extend(), .pick(), .omit(), .merge() |
| HTTP values | z.coerce.* for strings from query/form |
| NestJS | ZodValidationPipe in @Body() / @Query() |
| Errors | safeParse + flatten() |
| Exports | Barrel file in packages/shared |