Valibot
Library
Valibot
Overview
Valibot is a next-generation TypeScript validation library developed as a "modular and type-safe schema library". It provides functionality similar to Zod, Ajv, Joi, and Yup while achieving dramatic bundle size reduction through innovative modular design. Starting from under 700 bytes minimum with tree shaking and code splitting, it can reduce bundle size by up to 95% compared to Zod. As of 2025, it serves as a cutting-edge validation solution that meets the performance demands of modern web applications.
Details
Valibot 0.30 series is the latest stable version as of 2025, operating in JavaScript environments with zero external dependencies. Instead of relying on large functions and method chaining, it adopts an API design based on combining small, independent functions. Ensuring reliability with 100% test coverage, it provides extensive ecosystem support for React, Vue, Svelte, NestJS, DrizzleORM, and more. Pipeline-style validation (up to 20 actions) enables flexible combinations of checks and transformations.
Key Features
- Ultra-small Bundle Size: Starting from under 700 bytes with tree shaking
- Complete Type Safety: Full integration with TypeScript static type inference
- Modular Design: Innovative architecture importing only necessary functions
- Pipeline-style Validation: Flexible processing through action combinations
- Zero Dependencies: Pure implementation without external library dependencies
- Rich Ecosystem: Extensive integration with major frameworks
Pros and Cons
Pros
- Bundle size up to 95% smaller than Zod
- Performance optimized for modern web applications
- High reliability with 100% test coverage
- TypeScript-first development experience
- Complete optimization through tree shaking
- Integration with major framework ecosystems
Cons
- Relatively new library with limited available information
- Fewer adoption cases compared to Zod
- Somewhat steep learning curve (new pipeline concepts)
- Ecosystem still growing
- Design patterns for complex schemas still evolving
- Limited long-term support track record in production use
Reference Pages
Code Examples
Installation and Basic Setup
# Installing Valibot
npm install valibot
yarn add valibot
pnpm add valibot
bun add valibot
# Requires TypeScript 4.7 or higher
# Lightweight library with no dependencies
Basic Schema Definition and Validation
import {
object,
string,
number,
email,
minLength,
maxLength,
parse,
safeParse,
pipe,
InferInput,
InferOutput
} from 'valibot';
// Basic schema definition (modular format)
const UserSchema = object({
name: pipe(
string(),
minLength(2, 'Name must be at least 2 characters'),
maxLength(50, 'Name must be at most 50 characters')
),
age: pipe(
number(),
minValue(0, 'Age must be at least 0'),
maxValue(150, 'Age must be at most 150')
),
email: pipe(
string(),
email('Please enter a valid email address')
)
});
// Type inference (input and output types)
type UserInput = InferInput<typeof UserSchema>;
type UserOutput = InferOutput<typeof UserSchema>;
// Data validation
const userData = {
name: 'John Doe',
age: 30,
email: '[email protected]'
};
try {
// parse: throws exception on failure
const validUser = parse(UserSchema, userData);
console.log('Validation successful:', validUser);
} catch (error) {
console.error('Validation error:', error);
}
// safeParse: doesn't throw exceptions on failure
const result = safeParse(UserSchema, userData);
if (result.success) {
console.log('Validation successful:', result.output);
} else {
console.log('Validation failed:');
result.issues.forEach(issue => {
console.log(`${issue.path?.join('.')}: ${issue.message}`);
});
}
// Primitive type validation
import { boolean, date } from 'valibot';
const stringSchema = string();
const numberSchema = number();
const booleanSchema = boolean();
const dateSchema = date();
console.log(parse(stringSchema, "Hello")); // "Hello"
console.log(parse(numberSchema, 42)); // 42
console.log(parse(booleanSchema, true)); // true
console.log(parse(dateSchema, new Date())); // Date object
Advanced Validation and Pipeline Processing
import {
object,
string,
number,
array,
optional,
nullable,
pipe,
regex,
transform,
custom,
union,
literal,
minLength,
maxLength,
minValue,
maxValue,
email,
url
} from 'valibot';
// Complex pipeline validation
const PasswordSchema = pipe(
string(),
minLength(8, 'Password must be at least 8 characters'),
maxLength(128, 'Password must be at most 128 characters'),
regex(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/,
'Password must contain uppercase, lowercase, digit, and special characters'
)
);
// Custom validation
const EvenNumberSchema = pipe(
number(),
custom(
(value) => value % 2 === 0,
'Must be an even number'
)
);
// Pipeline with transformation
const TrimmedStringSchema = pipe(
string(),
transform((value) => value.trim()),
minLength(1, 'Empty string not allowed after trimming')
);
// Union types (multiple type choices)
const StatusSchema = union([
literal('pending'),
literal('approved'),
literal('rejected')
]);
// Optional and Nullable
const UserProfileSchema = object({
id: string(),
name: string(),
bio: optional(pipe(string(), maxLength(500))),
avatar: nullable(pipe(string(), url())),
age: optional(pipe(number(), minValue(13), maxValue(120))),
status: StatusSchema
});
// Array validation
const TagsSchema = pipe(
array(pipe(string(), minLength(1), maxLength(20))),
minLength(1, 'At least one tag is required'),
maxLength(10, 'Maximum 10 tags allowed')
);
// Nested objects
const AddressSchema = object({
street: pipe(string(), minLength(5)),
city: string(),
postalCode: pipe(
string(),
regex(/^\d{5}-\d{4}$/, 'Postal code must be in format 12345-6789')
),
country: pipe(string(), minLength(2))
});
const ComplexUserSchema = object({
personal: object({
firstName: pipe(string(), minLength(1)),
lastName: pipe(string(), minLength(1)),
email: pipe(string(), email())
}),
address: AddressSchema,
tags: TagsSchema,
preferences: object({
newsletter: boolean(),
theme: union([literal('light'), literal('dark'), literal('auto')])
})
});
// Complex data validation
const complexData = {
personal: {
firstName: 'John',
lastName: 'Doe',
email: '[email protected]'
},
address: {
street: '123 Main Street',
city: 'New York',
postalCode: '10001-1234',
country: 'USA'
},
tags: ['developer', 'typescript', 'javascript'],
preferences: {
newsletter: true,
theme: 'dark' as const
}
};
const complexResult = safeParse(ComplexUserSchema, complexData);
if (complexResult.success) {
console.log('Complex validation successful:', complexResult.output);
} else {
console.log('Complex validation errors:');
complexResult.issues.forEach(issue => {
console.log(`Field: ${issue.path?.join('.')}`);
console.log(`Message: ${issue.message}`);
});
}
Framework Integration (React, Vue, NestJS, etc.)
// React Hook Form integration
import { useForm } from 'react-hook-form';
import { valibotResolver } from '@hookform/resolvers/valibot';
import { object, string, number, pipe, minLength, email, minValue } from 'valibot';
const FormSchema = object({
name: pipe(string(), minLength(1, 'Name is required')),
email: pipe(string(), email('Please enter a valid email address')),
age: pipe(number(), minValue(18, 'Must be at least 18 years old'))
});
function UserForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: valibotResolver(FormSchema)
});
const onSubmit = (data: InferOutput<typeof FormSchema>) => {
console.log('Submitted data:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} placeholder="Name" />
{errors.name && <span>{errors.name.message}</span>}
<input {...register('email')} placeholder="Email" type="email" />
{errors.email && <span>{errors.email.message}</span>}
<input {...register('age', { valueAsNumber: true })} placeholder="Age" type="number" />
{errors.age && <span>{errors.age.message}</span>}
<button type="submit">Submit</button>
</form>
);
}
// Express.js middleware example
import express from 'express';
import { object, string, pipe, minLength, email } from 'valibot';
const app = express();
app.use(express.json());
const CreateUserSchema = object({
name: pipe(string(), minLength(1)),
email: pipe(string(), email()),
password: pipe(string(), minLength(8))
});
// Validation middleware
const validateRequest = (schema: any) => {
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
const result = safeParse(schema, req.body);
if (!result.success) {
return res.status(400).json({
error: 'Validation error',
issues: result.issues.map(issue => ({
field: issue.path?.join('.'),
message: issue.message
}))
});
}
req.body = result.output; // Set normalized data
next();
};
};
app.post('/users', validateRequest(CreateUserSchema), (req, res) => {
res.json({ message: 'User created successfully', user: req.body });
});
// NestJS DTO integration example
import { Injectable, PipeTransform, BadRequestException } from '@nestjs/common';
import { BaseSchema, safeParse } from 'valibot';
@Injectable()
export class ValibotValidationPipe implements PipeTransform {
constructor(private schema: BaseSchema<any, any, any>) {}
transform(value: any) {
const result = safeParse(this.schema, value);
if (!result.success) {
const errorMessages = result.issues.map(issue =>
`${issue.path?.join('.')}: ${issue.message}`
);
throw new BadRequestException({
message: 'Validation error',
errors: errorMessages
});
}
return result.output;
}
}
// NestJS Controller usage
import { Controller, Post, Body } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Post()
async createUser(@Body(new ValibotValidationPipe(CreateUserSchema)) userData: InferOutput<typeof CreateUserSchema>) {
return { message: 'User created', user: userData };
}
}
Custom Validation and Advanced Features
import {
custom,
transform,
pipe,
string,
number,
object,
array,
forward,
partialCheck,
BaseSchema,
BaseIssue
} from 'valibot';
// Custom validation function
const isUniqueEmail = (email: string): boolean => {
// In real applications, check database
const existingEmails = ['[email protected]', '[email protected]'];
return !existingEmails.includes(email.toLowerCase());
};
// Async custom validation (Valibot is sync-only, but showing pattern)
const EmailUniqueSchema = pipe(
string(),
email(),
custom(
(email) => isUniqueEmail(email),
'This email address is already in use'
)
);
// Complex conditional validation
const ConditionalUserSchema = object({
accountType: union([literal('personal'), literal('business')]),
name: string(),
companyName: optional(string()),
taxId: optional(string())
});
// Whole object validation
const BusinessAccountSchema = pipe(
ConditionalUserSchema,
partialCheck(
[['accountType'], ['companyName'], ['taxId']],
(input) => {
if (input.accountType === 'business') {
return !!(input.companyName && input.taxId);
}
return true;
},
'Business accounts require company name and tax ID'
)
);
// Schema with data transformation
const UserInputSchema = object({
name: pipe(
string(),
transform(name => name.trim()),
transform(name => name.replace(/\s+/g, ' ')), // Multiple spaces to single space
minLength(1)
),
email: pipe(
string(),
transform(email => email.toLowerCase()),
email()
),
tags: pipe(
string(),
transform(tags => tags.split(',').map(tag => tag.trim()).filter(tag => tag.length > 0)),
transform(tags => [...new Set(tags)]) // Remove duplicates
)
});
// Recursive schema (self-referencing)
type Category = {
id: string;
name: string;
parent?: Category;
children: Category[];
};
const CategorySchema: BaseSchema<any, Category, BaseIssue<unknown>> = object({
id: string(),
name: string(),
parent: optional(lazy(() => CategorySchema)),
children: array(lazy(() => CategorySchema))
});
// Performance-optimized schema
const OptimizedSchema = object({
// Basic validation only for speed
id: string(),
name: string(),
// Complex validation only when necessary
email: conditional(
(input) => typeof input === 'string' && input.includes('@'),
pipe(string(), email()),
string()
)
});
// Validation error customization
const CustomErrorSchema = object({
username: pipe(
string(),
minLength(3, 'Username must be at least 3 characters'),
maxLength(20, 'Username must be at most 20 characters'),
regex(
/^[a-zA-Z0-9_]+$/,
'Username can only contain letters, numbers, and underscores'
),
custom(
(username) => !['admin', 'root', 'system'].includes(username.toLowerCase()),
'This username is reserved'
)
)
});
// Detailed analysis of validation results
function analyzeValidationResult<T>(
schema: BaseSchema<any, T, any>,
data: unknown
): {
isValid: boolean;
data?: T;
errorSummary?: {
totalErrors: number;
fieldErrors: Record<string, string[]>;
globalErrors: string[];
};
} {
const result = safeParse(schema, data);
if (result.success) {
return {
isValid: true,
data: result.output
};
}
const fieldErrors: Record<string, string[]> = {};
const globalErrors: string[] = [];
result.issues.forEach(issue => {
if (issue.path && issue.path.length > 0) {
const fieldPath = issue.path.join('.');
if (!fieldErrors[fieldPath]) {
fieldErrors[fieldPath] = [];
}
fieldErrors[fieldPath].push(issue.message);
} else {
globalErrors.push(issue.message);
}
});
return {
isValid: false,
errorSummary: {
totalErrors: result.issues.length,
fieldErrors,
globalErrors
}
};
}
// Usage example
const testData = {
accountType: 'business' as const,
name: ' John Doe ',
companyName: '',
taxId: ''
};
const analysis = analyzeValidationResult(BusinessAccountSchema, testData);
console.log('Validation analysis result:', analysis);
Bundle Size Optimization and Performance
// Import only necessary functions (tree shaking optimization)
import { string, number, parse } from 'valibot';
// Minimal schema (minimum bundle size)
const MinimalSchema = object({
id: string(),
count: number()
});
// Feature-specific imports for optimization
import {
object,
string,
email, // Only when email validation needed
url, // Only when URL validation needed
minLength // Only when length validation needed
} from 'valibot';
// Conditional import pattern (dynamic import)
const createAdvancedSchema = async () => {
const { regex, transform, custom } = await import('valibot');
return object({
username: pipe(
string(),
transform(s => s.toLowerCase()),
regex(/^[a-z0-9_]+$/),
custom(s => s !== 'admin')
)
});
};
// Performance measurement
function benchmarkValidation<T>(
schema: BaseSchema<any, T, any>,
data: unknown,
iterations: number = 1000
): { averageTime: number; successRate: number } {
let successCount = 0;
const startTime = performance.now();
for (let i = 0; i < iterations; i++) {
const result = safeParse(schema, data);
if (result.success) {
successCount++;
}
}
const endTime = performance.now();
const averageTime = (endTime - startTime) / iterations;
const successRate = successCount / iterations;
return { averageTime, successRate };
}
// Batch validation (large data processing)
function validateBatch<T>(
schema: BaseSchema<any, T, any>,
dataArray: unknown[]
): {
valid: T[];
invalid: { data: unknown; issues: any[] }[];
summary: { total: number; validCount: number; invalidCount: number };
} {
const valid: T[] = [];
const invalid: { data: unknown; issues: any[] }[] = [];
for (const data of dataArray) {
const result = safeParse(schema, data);
if (result.success) {
valid.push(result.output);
} else {
invalid.push({ data, issues: result.issues });
}
}
return {
valid,
invalid,
summary: {
total: dataArray.length,
validCount: valid.length,
invalidCount: invalid.length
}
};
}
// Performance optimization through memoization
const schemaCache = new Map();
function getCachedSchema(schemaKey: string, schemaFactory: () => any) {
if (!schemaCache.has(schemaKey)) {
schemaCache.set(schemaKey, schemaFactory());
}
return schemaCache.get(schemaKey);
}
// Usage example
const userSchema = getCachedSchema('user', () =>
object({
name: string(),
email: pipe(string(), email())
})
);
// Streaming validation (real-time processing)
class StreamValidator<T> {
private schema: BaseSchema<any, T, any>;
private onValid: (data: T) => void;
private onInvalid: (data: unknown, issues: any[]) => void;
constructor(
schema: BaseSchema<any, T, any>,
handlers: {
onValid: (data: T) => void;
onInvalid: (data: unknown, issues: any[]) => void;
}
) {
this.schema = schema;
this.onValid = handlers.onValid;
this.onInvalid = handlers.onInvalid;
}
process(data: unknown): void {
const result = safeParse(this.schema, data);
if (result.success) {
this.onValid(result.output);
} else {
this.onInvalid(data, result.issues);
}
}
}
// Stream validator usage
const userStreamValidator = new StreamValidator(
object({ name: string(), age: number() }),
{
onValid: (user) => console.log('Valid user:', user),
onInvalid: (data, issues) => console.log('Invalid data:', data, issues)
}
);
// Real-time data processing
userStreamValidator.process({ name: 'John', age: 30 }); // Valid
userStreamValidator.process({ name: '', age: 'invalid' }); // Invalid