From harness-claude
Validates and transforms strings using Zod's min, max, email, url, regex, trim, startsWith, and custom error messages for user inputs like emails, URLs, passwords, and usernames.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Validate and transform strings with Zod's min, max, email, url, regex, trim, and custom error messages
Transforms and validates data using Zod's transform, refine, superRefine, and preprocess APIs. Guides reshaping outputs, custom validations, cross-field checks, and input preprocessing in TypeScript schemas.
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.
Validate and transform strings with Zod's min, max, email, url, regex, trim, and custom error messages
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');
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(),
});
.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');
.toLowerCase() and .toUpperCase() for normalization (these are transforms):const EmailSchema = z.string().trim().toLowerCase().email('Enter a valid email address');
.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'
.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');
.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
.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
.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'
.uuid() and .cuid() for ID format validation:const IdSchema = z.union([z.string().uuid(), z.string().cuid()]);
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:
.superRefine() on the parent object (see zod-transform-refine)zod-async-validation)