Claude Code Plugins

Community-maintained marketplace

Feedback

reviewing-patterns

@djankies/claude-configs
0
0

Review Zod schemas for correctness, performance, and v4 best practices

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name reviewing-patterns
description Review Zod schemas for correctness, performance, and v4 best practices
review true

Reviewing Zod Schemas

Purpose

Comprehensive code review checklist for Zod schemas ensuring v4 compliance, type safety, performance, and maintainability.

Review Checklist

1. API Compatibility

Check for Deprecated v3 Patterns

String format methods:

z.string().email()      // ❌ Deprecated
z.string().uuid()       // ❌ Deprecated
z.string().datetime()   // ❌ Deprecated

z.email()               // ✅ Correct v4
z.uuid()                // ✅ Correct v4
z.iso.datetime()        // ✅ Correct v4

Error customization:

z.string({ message: "Required" })               // ❌ Deprecated
z.string({ invalid_type_error: "Wrong type" })  // ❌ Deprecated
z.string({ required_error: "Missing" })         // ❌ Deprecated

z.string({ error: "Required" })                 // ✅ Correct v4

Schema composition:

schemaA.merge(schemaB)          // ❌ Deprecated
schemaA.extend(schemaB.shape)   // ✅ Correct v4

2. String Transformations

Check for Missing Transformations

User input should always be trimmed:

z.string()              // ❌ Missing trim
z.string().trim()       // ✅ Correct

Email and username normalization:

z.email()                           // ❌ Missing normalization
z.email().trim().toLowerCase()      // ✅ Correct

Code/identifier normalization:

z.string()                          // ❌ Missing normalization
z.string().trim().toUpperCase()     // ✅ Correct for codes

Manual vs. declarative transformations:

const trimmed = input.trim();
z.string().parse(trimmed);          // ❌ Manual transformation

z.string().trim().parse(input);     // ✅ Declarative

3. Type Safety

Type Inference

Check proper type extraction:

type User = typeof userSchema;              // ❌ Wrong
type User = z.infer<typeof userSchema>;     // ✅ Correct

Transform pipelines:

const schema = z.string().transform(s => parseInt(s));

type Input = z.input<typeof schema>;    // string
type Output = z.output<typeof schema>;  // number

Branded types for nominal typing:

const userId = z.string();              // ❌ Not branded
const userId = z.string().brand<'UserId'>();  // ✅ Branded

4. Error Handling

Parse vs. SafeParse

Anti-pattern:

try {
  const data = schema.parse(input);
} catch (error) {
  console.error(error);
}

Best practice:

const result = schema.safeParse(input);
if (!result.success) {
  console.error(result.error.flatten());
  return;
}
const data = result.data;

Error Messages

Check for user-friendly errors:

z.string()                              // ❌ Default error
z.string({ error: "Name is required" }) // ✅ Custom error

Complex error maps:

const userSchema = z.object({
  email: z.email({ error: "Please enter a valid email address" }),
  age: z.number({ error: "Age must be a number" }).min(18, {
    error: "Must be 18 or older"
  })
});

5. Performance

Schema Reuse

Anti-pattern:

function validateUser(data: unknown) {
  const schema = z.object({ email: z.email() });
  return schema.parse(data);
}

Best practice:

const userSchema = z.object({ email: z.email() });

function validateUser(data: unknown) {
  return userSchema.parse(data);
}

Array Validation

Anti-pattern:

items.forEach(item => schema.parse(item));

Best practice:

const arraySchema = z.array(schema);
arraySchema.parse(items);

Zod v4 bulk validation is 7x faster than item-by-item parsing.

Passthrough vs. Strict

Understand cost implications:

z.object({}).strict()       // Strips unknown keys (cost)
z.object({}).passthrough()  // Keeps unknown keys (faster)

Use .passthrough() when you don't need to strip unknown properties.

6. Schema Design

Appropriate Schema Types

Check for correct schema selection:

z.string().refine(s => s === 'true' || s === 'false')   // ❌ Complex
z.stringbool()                                           // ✅ Built-in v4 type

Union vs. Discriminated Union:

z.union([typeA, typeB])                 // ❌ Slower parsing

z.discriminatedUnion('type', [          // ✅ Faster
  z.object({ type: z.literal('a'), ...typeA }),
  z.object({ type: z.literal('b'), ...typeB })
])

Optional vs. Nullable vs. Nullish:

z.string().optional()       // string | undefined
z.string().nullable()       // string | null
z.string().nullish()        // string | null | undefined

Use the most specific type for your use case.

Schema Composition

Extend for adding fields:

const base = z.object({ id: z.string() });
const extended = base.extend({
  name: z.string(),
  email: z.email()
});

Pick/Omit for subsets:

const userSchema = z.object({
  id: z.string(),
  email: z.email(),
  password: z.string()
});

const publicUserSchema = userSchema.omit({ password: true });
const loginSchema = userSchema.pick({ email: true, password: true });

7. Validation Logic

Refinements

Check refinement usage:

z.string().refine(val => val.length > 5)    // ❌ Missing error

z.string().refine(
  val => val.length > 5,
  { error: "Must be at least 6 characters" }
)                                           // ✅ With error

Async refinements:

z.string().email().refine(
  async (email) => {
    return await checkEmailUnique(email);
  },
  { error: "Email already exists" }
)

Transformations

Check transformation order:

z.string().transform(s => s.toUpperCase()).trim()   // ❌ Wrong order
z.string().trim().transform(s => s.toUpperCase())   // ✅ Correct order

Transformations run after validation, built-in methods run first.

Type-safe transformations:

const schema = z.string().transform(s => parseInt(s));

type Output = z.output<typeof schema>;  // number

8. Security

Input Validation

Check entry point validation:

function handleFormSubmit(formData: FormData) {
  const data = {
    email: formData.get('email'),
    password: formData.get('password')
  };

  const result = loginSchema.safeParse(data);
  if (!result.success) {
    return { errors: result.error };
  }

  await login(result.data);
}

Avoid validation bypass:

function processUser(user: User) {
  await saveToDb(user);
}

processUser({ email: 'test@example.com' });     // ❌ No validation

Always validate at boundaries:

function processUser(data: unknown) {
  const result = userSchema.safeParse(data);
  if (!result.success) throw new Error('Invalid user');

  await saveToDb(result.data);
}

SQL Injection Prevention

Zod validates type/shape, NOT malicious content:

z.string()  // Allows: "'; DROP TABLE users; --"

Combine with sanitization:

import { sanitize } from 'sanitization-library';

const schema = z.string().transform(s => sanitize(s));

9. Testing

Schema Tests

Test valid inputs:

describe('userSchema', () => {
  it('accepts valid user', () => {
    const result = userSchema.safeParse({
      email: 'test@example.com',
      username: 'john'
    });
    expect(result.success).toBe(true);
  });
});

Test invalid inputs:

it('rejects invalid email', () => {
  const result = userSchema.safeParse({
    email: 'not-an-email',
    username: 'john'
  });
  expect(result.success).toBe(false);
  if (!result.success) {
    expect(result.error.issues[0].path).toEqual(['email']);
  }
});

Test transformations:

it('trims and lowercases email', () => {
  const schema = z.email().trim().toLowerCase();
  const result = schema.safeParse('  TEST@EXAMPLE.COM  ');

  expect(result.success).toBe(true);
  if (result.success) {
    expect(result.data).toBe('test@example.com');
  }
});

Review Process

Step 1: Automated Checks

Run validation skill:

/review zod-compatibility

Checks for:

  • Deprecated v3 APIs
  • Missing string transformations
  • Parse + try/catch anti-patterns

Step 2: Manual Review

Review each schema for:

  1. Appropriate schema types
  2. Error message quality
  3. Performance optimizations
  4. Type safety
  5. Security considerations

Step 3: Test Coverage

Ensure tests cover:

  • Valid input scenarios
  • Invalid input scenarios
  • Edge cases
  • Transformation behavior
  • Error message correctness

Common Issues

Issue 1: Missing Trim on User Input

Problem: Whitespace causes validation failures

Fix:

z.string().min(1)               // ❌ "   " passes
z.string().trim().min(1)        // ✅ "   " fails

Issue 2: Not Using SafeParse

Problem: Exceptions for invalid data

Fix:

try { schema.parse(data) }      // ❌ Exception-based
const result = schema.safeParse(data)  // ✅ Result-based

Issue 3: Schema Defined Inside Function

Problem: Schema recreated on every call

Fix:

function validate(data: unknown) {
  const schema = z.object({...});   // ❌ Recreated
  return schema.parse(data);
}

const schema = z.object({...});     // ✅ Module-level
function validate(data: unknown) {
  return schema.parse(data);
}

Issue 4: Wrong Type Extraction

Problem: Using schema type directly

Fix:

type User = typeof userSchema;          // ❌ ZodObject type
type User = z.infer<typeof userSchema>; // ✅ Inferred type

Integration with Review Plugin

This skill integrates with the review plugin via review: true frontmatter.

Invoke with:

/review zod

Or explicitly:

/review zod-schemas

References

  • Compatibility validation: Use the validating-schema-basics skill from the zod-4 plugin
  • v4 Features: Use the validating-string-formats skill from the zod-4 plugin
  • Error handling: Use the customizing-errors skill from the zod-4 plugin
  • Performance: Use the optimizing-performance skill from the zod-4 plugin
  • Testing: Use the testing-zod-schemas skill from the zod-4 plugin

Cross-Plugin References:

  • If reviewing TypeScript type safety alongside Zod schemas, use the reviewing-type-safety skill for comprehensive type validation patterns
  • If reviewing security concerns, use the reviewing-security skill for automated vulnerability scanning patterns

Success Criteria

  • ✅ No deprecated v3 APIs
  • ✅ String transformations used appropriately
  • ✅ safeParse pattern for error handling
  • ✅ Schema defined at module level
  • ✅ User-friendly error messages
  • ✅ Type inference correct
  • ✅ Comprehensive test coverage
  • ✅ Security validated at entry points