Harness-engineering zod-string-validation

Zod String Validation

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/codex/zod-string-validation" ~/.claude/skills/intense-visions-harness-engineering-zod-string-validation-9b5a28 && rm -rf "$T"
manifest: agents/skills/codex/zod-string-validation/SKILL.md
source content

Zod String Validation

Validate and transform strings with Zod's min, max, email, url, regex, trim, and custom error messages

When to Use

  • Validating user-facing string inputs: emails, URLs, passwords, usernames, phone numbers
  • Enforcing length constraints on text fields
  • Sanitizing strings by trimming or lowercasing before storing
  • Providing user-friendly error messages instead of Zod's defaults

Instructions

  1. Start with
    z.string()
    and chain validators in order from most permissive to most restrictive:
import { z } from 'zod';

const UsernameSchema = z
  .string()
  .min(3, 'Username must be at least 3 characters')
  .max(20, 'Username cannot exceed 20 characters')
  .regex(/^[a-z0-9_]+$/, 'Only lowercase letters, numbers, and underscores');
  1. Use built-in format validators for common patterns:
const ContactSchema = z.object({
  email: z.string().email('Enter a valid email address'),
  website: z.string().url('Enter a valid URL').optional(),
  phone: z
    .string()
    .regex(/^\+?[1-9]\d{7,14}$/, 'Enter a valid phone number')
    .optional(),
});
  1. Chain
    .trim()
    before length checks so whitespace does not count toward limits:
const TitleSchema = z
  .string()
  .trim()
  .min(1, 'Title is required')
  .max(100, 'Title cannot exceed 100 characters');
  1. Use
    .toLowerCase()
    and
    .toUpperCase()
    for normalization (these are transforms):
const EmailSchema = z.string().trim().toLowerCase().email('Enter a valid email address');
  1. Use
    .transform()
    for custom string shaping — the output type can differ from string:
const SlugSchema = z
  .string()
  .trim()
  .toLowerCase()
  .transform((val) => val.replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''));

// SlugSchema.parse('  Hello World! ') → 'hello-world'
  1. Use
    .startsWith()
    and
    .endsWith()
    for prefix/suffix constraints:
const ApiKeySchema = z
  .string()
  .startsWith('sk_', 'API keys must start with sk_')
  .length(32, 'API key must be exactly 32 characters');
  1. Use
    .includes()
    for substring requirements:
const PasswordSchema = z
  .string()
  .min(8, 'Password must be at least 8 characters')
  .includes('@', { message: 'Password must contain @' }); // rarely used, prefer regex
  1. Use
    .ip()
    for IP address validation (v4, v6, or both):
const IpSchema = z.string().ip({ version: 'v4', message: 'Must be a valid IPv4 address' });
const AnyIpSchema = z.string().ip(); // accepts v4 and v6
  1. Use
    .datetime()
    for ISO 8601 datetime strings:
const TimestampSchema = z.string().datetime({ message: 'Must be ISO 8601 format' });
// Accepts: '2024-01-15T10:30:00Z', '2024-01-15T10:30:00.000Z'
  1. Use
    .uuid()
    and
    .cuid()
    for ID format validation:
const IdSchema = z.union([z.string().uuid(), z.string().cuid()]);

Details

Custom error messages:

Every Zod string method accepts either a string message or an options object:

// Short form
z.string().min(8, 'Too short');

// Long form — useful when you need to customize the error code or path
z.string().min(8, { message: 'Password must be at least 8 characters', path: ['password'] });

Order matters for transforms:

.trim()
,
.toLowerCase()
, and
.toUpperCase()
are transforms under the hood. They run in order during parsing. Always put them before validation checks:

// Correct: trim before min check
z.string().trim().min(1, 'Required');

// Wrong: min check runs on untrimmed string, '   ' passes
z.string().min(1).trim();

Distinguishing empty vs absent:

// Required, non-empty string
const Required = z.string().min(1, 'This field is required');

// Optional but non-empty when present
const OptionalNonEmpty = z.string().min(1).optional();

// Empty string treated as absent (common for HTML forms)
const FormField = z
  .string()
  .transform((val) => (val === '' ? undefined : val))
  .optional();

Regex complexity:

For complex patterns, extract the regex to a named constant for testability:

const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;

const SlugSchema = z.string().regex(SLUG_PATTERN, 'Invalid slug format');

When NOT to use string validators directly:

  • For object-level string fields with cross-field dependencies — use
    .superRefine()
    on the parent object (see
    zod-transform-refine
    )
  • For async uniqueness checks (e.g., email already taken) — use async refinements (see
    zod-async-validation
    )

Source

https://zod.dev/api#strings

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.