valibot
Valibot
Modular schema validation library with best-in-class bundle size (~1KB for basic forms).
Quick Start
npm install valibot
import * as v from 'valibot';
const schema = v.object({
name: v.pipe(v.string(), v.minLength(2)),
email: v.pipe(v.string(), v.email()),
age: v.pipe(v.number(), v.minValue(0)),
});
// Validate
const result = v.safeParse(schema, data);
if (result.success) {
console.log(result.output);
} else {
console.log(result.issues);
}
Schema Types
Primitives
import * as v from 'valibot';
// String
v.string()
v.pipe(v.string(), v.minLength(1), v.maxLength(100))
// Number
v.number()
v.pipe(v.number(), v.minValue(0), v.maxValue(100))
// Boolean
v.boolean()
// Null/Undefined
v.null_()
v.undefined_()
// BigInt
v.bigint()
// Symbol
v.symbol()
// Date
v.date()
v.pipe(v.date(), v.minValue(new Date()))
String Validations
v.pipe(
v.string(),
v.minLength(1, 'Required'),
v.maxLength(100, 'Too long'),
v.email('Invalid email'),
v.url('Invalid URL'),
v.uuid('Invalid UUID'),
v.regex(/^[a-z]+$/, 'Only lowercase'),
v.startsWith('https://'),
v.endsWith('.com'),
v.includes('@'),
v.trim(),
v.toLowerCase(),
v.toUpperCase(),
)
Number Validations
v.pipe(
v.number(),
v.minValue(0, 'Must be positive'),
v.maxValue(100, 'Max 100'),
v.integer('Must be integer'),
v.multipleOf(5, 'Must be multiple of 5'),
v.finite('Must be finite'),
v.safeInteger('Must be safe integer'),
)
Object
const userSchema = v.object({
name: v.string(),
email: v.pipe(v.string(), v.email()),
age: v.optional(v.number()),
});
// Strict (no extra keys)
const strictSchema = v.strictObject({
id: v.number(),
name: v.string(),
});
// Loose (allow extra keys)
const looseSchema = v.looseObject({
id: v.number(),
});
Array
v.array(v.string())
v.pipe(
v.array(v.string()),
v.minLength(1, 'At least one item'),
v.maxLength(10, 'Max 10 items'),
)
// Tuple
v.tuple([v.string(), v.number()])
Union & Intersection
// Union (OR)
const statusSchema = v.union([
v.literal('active'),
v.literal('inactive'),
v.literal('pending'),
]);
// Variant (discriminated union)
const eventSchema = v.variant('type', [
v.object({ type: v.literal('click'), x: v.number(), y: v.number() }),
v.object({ type: v.literal('scroll'), offset: v.number() }),
]);
// Intersection (AND)
const combined = v.intersect([
v.object({ id: v.number() }),
v.object({ name: v.string() }),
]);
Optional & Nullable
// Optional (undefined allowed)
v.optional(v.string())
// Nullable (null allowed)
v.nullable(v.string())
// Nullish (null or undefined)
v.nullish(v.string())
// With default
v.optional(v.string(), 'default value')
v.nullable(v.number(), 0)
Pipe System
Valibot uses pipe() to chain validations and transformations:
const schema = v.pipe(
v.string(),
v.trim(), // Transform
v.minLength(1), // Validate
v.toLowerCase(), // Transform
v.email(), // Validate
);
// Order matters!
const result = v.parse(schema, ' John@Example.COM ');
// Output: 'john@example.com'
Validation
parse() - Throws on Error
try {
const data = v.parse(schema, input);
} catch (error) {
if (error instanceof v.ValiError) {
console.log(error.issues);
}
}
safeParse() - Returns Result
const result = v.safeParse(schema, input);
if (result.success) {
console.log(result.output);
} else {
console.log(result.issues);
}
is() - Type Guard
if (v.is(schema, input)) {
// input is typed
}
Type Inference
import * as v from 'valibot';
const userSchema = v.object({
id: v.number(),
name: v.string(),
email: v.pipe(v.string(), v.email()),
roles: v.array(v.string()),
});
// Infer TypeScript type
type User = v.InferOutput<typeof userSchema>;
// { id: number; name: string; email: string; roles: string[] }
// Infer input type (before transforms)
type UserInput = v.InferInput<typeof userSchema>;
Custom Validation
check() - Custom Predicate
const schema = v.pipe(
v.string(),
v.check((value) => value.includes('@'), 'Must contain @'),
);
transform() - Custom Transform
const schema = v.pipe(
v.string(),
v.transform((value) => value.split(',').map((s) => s.trim())),
);
Custom Schema
const passwordSchema = v.pipe(
v.string(),
v.minLength(8, 'At least 8 characters'),
v.regex(/[A-Z]/, 'Need uppercase'),
v.regex(/[a-z]/, 'Need lowercase'),
v.regex(/[0-9]/, 'Need number'),
);
Common Patterns
User Registration
const registrationSchema = v.object({
email: v.pipe(
v.string(),
v.email('Invalid email'),
),
password: v.pipe(
v.string(),
v.minLength(8, 'At least 8 characters'),
v.regex(/[A-Z]/, 'Need uppercase'),
v.regex(/[0-9]/, 'Need number'),
),
confirmPassword: v.string(),
age: v.pipe(
v.number(),
v.minValue(18, 'Must be 18+'),
),
terms: v.pipe(
v.boolean(),
v.value(true, 'Must accept terms'),
),
});
// Add cross-field validation
const fullSchema = v.pipe(
registrationSchema,
v.forward(
v.partialCheck(
[['password'], ['confirmPassword']],
(input) => input.password === input.confirmPassword,
'Passwords must match',
),
['confirmPassword'],
),
);
API Response
const apiResponseSchema = v.object({
success: v.boolean(),
data: v.optional(v.object({
users: v.array(v.object({
id: v.number(),
name: v.string(),
})),
})),
error: v.optional(v.string()),
});
Environment Variables
const envSchema = v.object({
NODE_ENV: v.picklist(['development', 'production', 'test']),
PORT: v.pipe(v.string(), v.transform(Number), v.integer()),
DATABASE_URL: v.pipe(v.string(), v.url()),
API_KEY: v.pipe(v.string(), v.minLength(32)),
});
const env = v.parse(envSchema, process.env);
React Hook Form Integration
import { useForm } from 'react-hook-form';
import { valibotResolver } from '@hookform/resolvers/valibot';
import * as v from 'valibot';
const schema = v.object({
email: v.pipe(v.string(), v.email()),
password: v.pipe(v.string(), v.minLength(8)),
});
type FormData = v.InferOutput<typeof schema>;
function Form() {
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: valibotResolver(schema),
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email')} />
{errors.email && <span>{errors.email.message}</span>}
<input {...register('password')} type="password" />
{errors.password && <span>{errors.password.message}</span>}
<button type="submit">Submit</button>
</form>
);
}
Bundle Size Comparison
| Library | Basic Form |
|---|---|
| Valibot | ~1.4 KB |
| Zod | ~12 KB |
| Yup | ~15 KB |
Valibot achieves this through modular imports - only used functions are bundled.
See references/methods.md for complete method reference.
More from mgd34msu/goodvibes-gemini
chakra-ui
Builds accessible React applications with Chakra UI v3 components, tokens, and recipes. Use when creating styled component systems, theming, or accessible form controls.
70fastify
Builds high-performance Node.js APIs with Fastify, TypeScript, schema validation, and plugins. Use when building fast REST APIs, microservices, or needing schema-based validation.
2code-smell-detector
Detects code smells, anti-patterns, and common bugs with quantified thresholds and severity scoring. Use when reviewing code quality, finding maintainability issues, detecting SOLID violations, or identifying technical debt.
2playwright
Tests web applications with Playwright including E2E tests, locators, assertions, and visual testing. Use when writing end-to-end tests, testing across browsers, automating user flows, or debugging test failures.
2vitest
Tests JavaScript and TypeScript applications with Vitest including unit tests, mocking, coverage, and React component testing. Use when writing tests, setting up test infrastructure, mocking dependencies, or measuring code coverage.
2vite
Builds web applications with Vite including dev server, production builds, plugins, and configuration. Use when scaffolding projects, configuring build tools, optimizing bundles, or setting up development environments.
2