Loading...
Loading...
Schema validation with Valibot, the modular and type-safe schema library. Use when the user needs to validate data, create schemas, parse inputs, or work with Valibot in their project. Also use when migrating from Zod to Valibot.
npx skill4agent add open-circle/agent-skills valibot-usage| Feature | Zod ❌ | Valibot ✅ |
|---|---|---|
| Import | | |
| Validations | Chained methods: | Pipeline: |
| Parsing | | |
| Safe parsing | | |
| Optional | | |
| Nullable | | |
| Default | | |
| Transform | | |
| Refine/Check | | |
| Enum | | |
| Native enum | | |
| Union | | |
| Discriminated union | | |
| Intersection | | |
| Min/max length | | |
| Min/max value | | |
| Infer type | | |
| Infer input | | |
// ❌ WRONG - This is Zod syntax, NOT Valibot!
const Schema = v.string().email().min(5);
const result = Schema.parse(data);
// ✅ CORRECT - Valibot uses functions and pipelines
const Schema = v.pipe(v.string(), v.email(), v.minLength(5));
const result = v.parse(Schema, data);// ❌ WRONG - Zod-style optional
const Schema = v.object({
name: v.string().optional(),
});
// ✅ CORRECT - Valibot wraps with optional()
const Schema = v.object({
name: v.optional(v.string()),
});// ❌ WRONG - Zod-style default
const Schema = v.string().default("hello");
// ✅ CORRECT - Valibot uses second argument
const Schema = v.optional(v.string(), "hello");npm install valibot # npm
yarn add valibot # yarn
pnpm add valibot # pnpm
bun add valibot # bunimport * as v from "valibot";import { object, string, pipe, email, parse } from "valibot";import * as v from "valibot";
// Primitive schemas
const StringSchema = v.string();
const NumberSchema = v.number();
const BooleanSchema = v.boolean();
const DateSchema = v.date();
// Complex schemas
const ArraySchema = v.array(v.string());
const ObjectSchema = v.object({
name: v.string(),
age: v.number(),
});// Parsing
const result = v.parse(StringSchema, "hello");
const safeResult = v.safeParse(StringSchema, "hello");
// Type guard
if (v.is(StringSchema, data)) {
// data is typed as string
}pipe()// Actions are used in pipe()
const EmailSchema = v.pipe(
v.string(),
v.trim(),
v.email(),
v.endsWith("@example.com"),
);import * as v from "valibot";
const UsernameSchema = v.pipe(
v.string(),
v.trim(),
v.minLength(3, "Username must be at least 3 characters"),
v.maxLength(20, "Username must be at most 20 characters"),
v.regex(
/^[a-z0-9_]+$/i,
"Username can only contain letters, numbers, and underscores",
),
);
const AgeSchema = v.pipe(
v.number(),
v.integer("Age must be a whole number"),
v.minValue(0, "Age cannot be negative"),
v.maxValue(150, "Age cannot exceed 150"),
);v.email()v.url()v.uuid()v.regex(pattern)v.minLength(n)v.maxLength(n)v.length(n)v.nonEmpty()v.startsWith(str)v.endsWith(str)v.includes(str)v.minValue(n)v.maxValue(n)v.gtValue(n)v.ltValue(n)v.integer()v.finite()v.safeInteger()v.multipleOf(n)v.minLength(n)v.maxLength(n)v.length(n)v.nonEmpty()v.includes(item)v.excludes(item)const PasswordSchema = v.pipe(
v.string(),
v.minLength(8),
v.check(
(input) => /[A-Z]/.test(input),
"Password must contain an uppercase letter",
),
v.check((input) => /[0-9]/.test(input), "Password must contain a number"),
);v.trim()v.trimStart()v.trimEnd()v.toLowerCase()v.toUpperCase()v.toMinValue(n)v.toMaxValue(n)const NormalizedEmailSchema = v.pipe(
v.string(),
v.trim(),
v.toLowerCase(),
v.email(),
);
// Clamp number to range 0-100
const PercentageSchema = v.pipe(v.number(), v.toMinValue(0), v.toMaxValue(100));v.toNumber()v.toString()v.toBoolean()v.toBigint()v.toDate()// Convert string to number
const PortSchema = v.pipe(v.string(), v.toNumber(), v.integer(), v.minValue(1));
// Convert ISO string to Date
const TimestampSchema = v.pipe(v.string(), v.isoDateTime(), v.toDate());
// Convert to boolean
const FlagSchema = v.pipe(v.string(), v.toBoolean());v.transform()const DateStringSchema = v.pipe(
v.string(),
v.isoDate(),
v.transform((input) => new Date(input)),
);
// Custom object transformation
const UserSchema = v.pipe(
v.object({
firstName: v.string(),
lastName: v.string(),
}),
v.transform((input) => ({
...input,
fullName: `${input.firstName} ${input.lastName}`,
})),
);const UserSchema = v.object({
id: v.number(),
name: v.string(),
email: v.pipe(v.string(), v.email()),
age: v.optional(v.number()),
});
type User = v.InferOutput<typeof UserSchema>;// Regular object - strips unknown keys (default)
const ObjectSchema = v.object({ key: v.string() });
// Loose object - allows and preserves unknown keys
const LooseObjectSchema = v.looseObject({ key: v.string() });
// Strict object - throws on unknown keys
const StrictObjectSchema = v.strictObject({ key: v.string() });
// Object with rest - validates unknown keys against a schema
const ObjectWithRestSchema = v.objectWithRest(
{ key: v.string() },
v.number(), // unknown keys must be numbers
);const ProfileSchema = v.object({
// Required
name: v.string(),
// Optional (can be undefined or missing)
nickname: v.optional(v.string()),
// Optional with default
role: v.optional(v.string(), "user"),
// Nullable (can be null)
avatar: v.nullable(v.string()),
// Nullish (can be null or undefined)
bio: v.nullish(v.string()),
// Nullish with default
theme: v.nullish(v.string(), "light"),
});const BaseSchema = v.object({
id: v.number(),
name: v.string(),
email: v.string(),
password: v.string(),
});
// Pick specific keys
const PublicUserSchema = v.pick(BaseSchema, ["id", "name"]);
// Omit specific keys
const UserWithoutPasswordSchema = v.omit(BaseSchema, ["password"]);
// Make all optional
const PartialUserSchema = v.partial(BaseSchema);
// Make all required
const RequiredUserSchema = v.required(PartialUserSchema);
// Merge objects
const ExtendedUserSchema = v.object({
...BaseSchema.entries,
createdAt: v.date(),
});const RegistrationSchema = v.pipe(
v.object({
password: v.pipe(v.string(), v.minLength(8)),
confirmPassword: v.string(),
}),
v.forward(
v.partialCheck(
[["password"], ["confirmPassword"]],
(input) => input.password === input.confirmPassword,
"Passwords do not match",
),
["confirmPassword"],
),
);const TagsSchema = v.pipe(
v.array(v.string()),
v.minLength(1, "At least one tag required"),
v.maxLength(10, "Maximum 10 tags allowed"),
);
// Array of objects
const UsersSchema = v.array(
v.object({
id: v.number(),
name: v.string(),
}),
);// Fixed-length array with specific types
const CoordinatesSchema = v.tuple([v.number(), v.number()]);
// Type: [number, number]
// Tuple with rest
const ArgsSchema = v.tupleWithRest(
[v.string()], // first arg is string
v.number(), // rest are numbers
);
// Type: [string, ...number[]]const StringOrNumberSchema = v.union([v.string(), v.number()]);
const StatusSchema = v.union([
v.literal("pending"),
v.literal("active"),
v.literal("inactive"),
]);// Simpler than union of literals
const StatusSchema = v.picklist(["pending", "active", "inactive"]);
const PrioritySchema = v.picklist([1, 2, 3]);variantconst EventSchema = v.variant("type", [
v.object({
type: v.literal("click"),
x: v.number(),
y: v.number(),
}),
v.object({
type: v.literal("keypress"),
key: v.string(),
}),
v.object({
type: v.literal("scroll"),
direction: v.picklist(["up", "down"]),
}),
]);import * as v from "valibot";
const EmailSchema = v.pipe(v.string(), v.email());
try {
const email = v.parse(EmailSchema, "jane@example.com");
console.log(email); // 'jane@example.com'
} catch (error) {
console.error(error); // ValiError
}const result = v.safeParse(EmailSchema, input);
if (result.success) {
console.log(result.output); // Valid data
} else {
console.log(result.issues); // Array of issues
}if (v.is(EmailSchema, input)) {
// input is typed as string
}// Abort early - stop at first error
v.parse(Schema, data, { abortEarly: true });
// Abort pipe early - stop pipeline at first error
v.parse(Schema, data, { abortPipeEarly: true });import * as v from "valibot";
const UserSchema = v.object({
name: v.string(),
age: v.pipe(v.string(), v.transform(Number)),
role: v.optional(v.string(), "user"),
});
// Output type (after transformations and defaults)
type User = v.InferOutput<typeof UserSchema>;
// { name: string; age: number; role: string }
// Input type (before transformations)
type UserInput = v.InferInput<typeof UserSchema>;
// { name: string; age: string; role?: string | undefined }
// Issue type
type UserIssue = v.InferIssue<typeof UserSchema>;const LoginSchema = v.object({
email: v.pipe(
v.string("Email must be a string"),
v.nonEmpty("Please enter your email"),
v.email("Invalid email format"),
),
password: v.pipe(
v.string("Password must be a string"),
v.nonEmpty("Please enter your password"),
v.minLength(8, "Password must be at least 8 characters"),
),
});const result = v.safeParse(LoginSchema, data);
if (!result.success) {
const flat = v.flatten(result.issues);
// { nested: { email: ['Invalid email format'], password: ['...'] } }
}kindtypeinputexpectedreceivedmessagepath// Static fallback
const NumberSchema = v.fallback(v.number(), 0);
v.parse(NumberSchema, "invalid"); // Returns 0
// Dynamic fallback
const DateSchema = v.fallback(v.date(), () => new Date());import * as v from "valibot";
type TreeNode = {
value: string;
children: TreeNode[];
};
const TreeNodeSchema: v.GenericSchema<TreeNode> = v.object({
value: v.string(),
children: v.lazy(() => v.array(TreeNodeSchema)),
});import * as v from "valibot";
const isUsernameAvailable = async (username: string) => {
// Check database
return true;
};
const UsernameSchema = v.pipeAsync(
v.string(),
v.minLength(3),
v.checkAsync(isUsernameAvailable, "Username is already taken"),
);
// Must use parseAsync
const username = await v.parseAsync(UsernameSchema, "john");import { toJsonSchema } from "@valibot/to-json-schema";
import * as v from "valibot";
const EmailSchema = v.pipe(v.string(), v.email());
const jsonSchema = toJsonSchema(EmailSchema);
// { type: 'string', format: 'email' }export const User = v.object({
name: v.string(),
email: v.pipe(v.string(), v.email()),
});
export type User = v.InferOutput<typeof User>;
// Usage
const users: User[] = [];
users.push(v.parse(User, data));export const UserSchema = v.object({
name: v.string(),
age: v.pipe(v.string(), v.transform(Number)),
});
export type UserInput = v.InferInput<typeof UserSchema>;
export type UserOutput = v.InferOutput<typeof UserSchema>;const LoginSchema = v.object({
email: v.pipe(
v.string(),
v.nonEmpty("Please enter your email"),
v.email("Invalid email address"),
),
password: v.pipe(
v.string(),
v.nonEmpty("Please enter your password"),
v.minLength(8, "Password must be at least 8 characters"),
),
});const ApiResponseSchema = v.variant("status", [
v.object({
status: v.literal("success"),
data: v.unknown(),
}),
v.object({
status: v.literal("error"),
error: v.object({
code: v.string(),
message: v.string(),
}),
}),
]);const EnvSchema = v.object({
NODE_ENV: v.picklist(["development", "production", "test"]),
PORT: v.pipe(v.string(), v.transform(Number), v.integer(), v.minValue(1)),
DATABASE_URL: v.pipe(v.string(), v.url()),
API_KEY: v.pipe(v.string(), v.minLength(32)),
});
const env = v.parse(EnvSchema, process.env);// String to Date
const DateFromStringSchema = v.pipe(
v.string(),
v.isoDate(),
v.transform((input) => new Date(input)),
);
// Date validation
const FutureDateSchema = v.pipe(
v.date(),
v.minValue(new Date(), "Date must be in the future"),
);