Show HN: FormData validation for TypeScript (Zod's most requested feature)
4 hours ago
1
Zero-dependency TypeScript validation with features Zod doesn't have. FormData validation, partial validation, JSON Schema export, and more. Perfect for Remix/Next.js.
We implemented the TOP REQUESTED features from Zod's issue tracker:
New in v2.1 (Just Added!)
✅ FormData validation (60+ votes) - Zod doesn't have it!
✅ Partial validation - Return valid data even with errors
✅ JSON Schema export - Better than zod-to-json-schema
✅ Schema metadata extraction - Auto-generate forms
✅ XOR (exclusive OR) - Cleaner than Zod's approach
✅ File validation - Size & MIME type checking
✅ Schema descriptions - Rich metadata support
From v2.0 (Already Have It!)
✅ Circular/recursive validation (Zod Issue #5346) - They don't have it yet
✅ Clearer optional/nullable API (Zod Issue #5348) - Less confusing than Zod
import{validateFormData,object,string,number,file}from'lite-schema-check/v2';constschema=object({name: string.min(3),age: number.positive(),avatar: file({maxSize: 5_000_000,mimeTypes: ['image/png','image/jpeg']})});// In Remix/Next.js server action:exportasyncfunctionaction({ request }){constformData=awaitrequest.formData();constresult=validateFormData(formData,schema);if(result.success){// Automatic type conversion! age is number, not stringawaitcreateUser(result.data);}}
🔄 Partial Validation (NEW!)
import{validatePartial,object,string,number}from'lite-schema-check/v2';constschema=object({name: string.min(3),email: string.email(),age: number.positive()});constdata={name: 'John',// ✅ Validemail: 'not-email',// ❌ Invalidage: 30// ✅ Valid};constresult=validatePartial(data,schema);// Save what's valid, show errors for what's notawaitsaveDraft(result.validData);// { name: 'John', age: 30 }showErrors(result.invalidFields);// { email: {...}}
📄 JSON Schema Export (NEW!)
import{toJSONSchema,describe,optional}from'lite-schema-check/v2';constschema=object({name: describe(string.min(3),'User full name'),age: optional(number.positive())});constjsonSchema=toJSONSchema(schema);// {// type: 'object',// properties: {...},// required: ['name'] // ✅ 'age' correctly NOT required!// }
import{validate,object,optional,array,union,literal}from'lite-schema-check/v2';// Define a schema with all the featuresconstuserSchema=object({name: 'string',age: 'number',email: optional('string'),// Can be missingtags: array('string',{min: 1,max: 10}),role: union(literal('admin'),literal('user'),literal('guest')),profile: object({bio: 'string',website: optional('string')})});// Validate dataconstresult=validate(userData,userSchema);if(result.success){console.log('Valid!',result.data);}else{console.log('Errors:',result.errors);// Detailed error paths: ['profile', 'bio']}
Problem: Zod returns nothing if ANY field fails. Can't save partial data.
Our Solution:
import{validatePartial}from'lite-schema-check/v2';constresult=validatePartial(data,schema);// Returns:// {// validData: { name: 'John', age: 30 }, // Save these!// invalidFields: { email: {...}} // Show errors// }awaitsaveDraft(result.validData);// Save what works
Use Cases: Auto-save forms, progressive validation, data migration
Status: ✅ We have it | ❌ Zod doesn't
3. 🔥 JSON Schema Export (Better than zod-to-json-schema)
Status: ✅ We have it | ❌ Zod doesn't (issue open since Oct 16, 2025)
2. ✅ Clearer Optional/Nullable API (Zod Issue #5348)
Problem: Zod's .required() on .optional() confuses users.
Our Solution:
import{object,optional,nullable,nullish}from'lite-schema-check/v2';constschema=object({email: optional('string'),// Can be missing entirelyphone: nullable('string'),// Can be null (must be present)bio: nullish('string')// Can be null OR undefined});
Problem: Zod transforms are one-way. Users want to serialize back.
Our Solution:
import{codec}from'lite-schema-check/v2';constdateCodec=codec('string',(str)=>newDate(str),// Parse (decode)(date)=>date.toISOString()// Serialize (encode));// Can parse API responses AND serialize back!
Status: ✅ Bidirectional | ❌ Zod is one-way only
import{object,array}from'lite-schema-check/v2';constschema=object({tags: array('string',{min: 1,max: 5})});validate({tags: ['js','ts']});// ✅ Validvalidate({tags: []});// ❌ Too fewvalidate({tags: ['a','b','c','d','e','f']});// ❌ Too many