valibot by open-circle/agent-skills
npx skills add https://github.com/open-circle/agent-skills --skill valibot此技能帮助您高效使用 Valibot,这是一个用于验证结构化数据的模块化且类型安全的模式库。
Valibot 和 Zod 有不同的 API。切勿将它们混淆!
| 功能特性 | Zod ❌ | Valibot ✅ |
|---|---|---|
| 导入 | import { z } from 'zod' | import * as v from 'valibot' |
| 验证 | 链式方法:.email().min(5) | 管道: |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
v.pipe(v.string(), v.email(), v.minLength(5))| 解析 | schema.parse(data) | v.parse(schema, data) |
| 安全解析 | schema.safeParse(data) | v.safeParse(schema, data) |
| 可选 | z.string().optional() | v.optional(v.string()) |
| 可为空 | z.string().nullable() | v.nullable(v.string()) |
| 默认值 | z.string().default('x') | v.optional(v.string(), 'x') |
| 转换 | z.string().transform(fn) | v.pipe(v.string(), v.transform(fn)) |
| 细化/检查 | z.string().refine(fn) | v.pipe(v.string(), v.check(fn)) |
| 枚举 | z.enum(['a', 'b']) | v.picklist(['a', 'b']) |
| 原生枚举 | z.nativeEnum(MyEnum) | v.enum(MyEnum) |
| 联合类型 | z.union([a, b]) | v.union([a, b]) |
| 可区分联合类型 | z.discriminatedUnion('type', [...]) | v.variant('type', [...]) |
| 交叉类型 | z.intersection(a, b) | v.intersect([a, b]) |
| 最小/最大长度 | .min(5).max(10) | v.minLength(5), v.maxLength(10) |
| 最小/最大值 | .gte(5).lte(10) | v.minValue(5), v.maxValue(10) |
| 推断输出类型 | z.infer<typeof Schema> | v.InferOutput<typeof Schema> |
| 推断输入类型 | z.input<typeof Schema> | v.InferInput<typeof Schema> |
// ❌ 错误 - 这是 Zod 语法,不是 Valibot!
const Schema = v.string().email().min(5);
const result = Schema.parse(data);
// ✅ 正确 - Valibot 使用函数和管道
const Schema = v.pipe(v.string(), v.email(), v.minLength(5));
const result = v.parse(Schema, data);
// ❌ 错误 - Zod 风格的可选字段
const Schema = v.object({
name: v.string().optional(),
});
// ✅ 正确 - Valibot 使用 optional() 包装
const Schema = v.object({
name: v.optional(v.string()),
});
// ❌ 错误 - Zod 风格的默认值
const Schema = v.string().default("hello");
// ✅ 正确 - Valibot 使用第二个参数
const Schema = v.optional(v.string(), "hello");
npm install valibot # npm
yarn add valibot # yarn
pnpm add valibot # pnpm
bun add valibot # bun
使用通配符导入(推荐):
import * as v from "valibot";
或者使用单独导入:
import { object, string, pipe, email, parse } from "valibot";
Valibot 的 API 分为三个主要概念:
模式定义了预期的数据类型。它们是起点。
import * as v from "valibot";
// 基本类型模式
const StringSchema = v.string();
const NumberSchema = v.number();
const BooleanSchema = v.boolean();
const DateSchema = v.date();
// 复杂模式
const ArraySchema = v.array(v.string());
const ObjectSchema = v.object({
name: v.string(),
age: v.number(),
});
方法帮助您使用或修改模式。模式始终是第一个参数。
// 解析
const result = v.parse(StringSchema, "hello");
const safeResult = v.safeParse(StringSchema, "hello");
// 类型守卫
if (v.is(StringSchema, data)) {
// data 被类型化为 string
}
操作在 pipe() 内验证或转换数据。它们必须在管道内部使用。
// 操作在 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, "用户名至少需要 3 个字符"),
v.maxLength(20, "用户名最多 20 个字符"),
v.regex(
/^[a-z0-9_]+$/i,
"用户名只能包含字母、数字和下划线",
),
);
const AgeSchema = v.pipe(
v.number(),
v.integer("年龄必须是整数"),
v.minValue(0, "年龄不能为负数"),
v.maxValue(150, "年龄不能超过 150"),
);
字符串验证:
v.email() — 有效的电子邮件格式v.url() — 有效的 URL 格式v.uuid() — 有效的 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) — 必须是 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),
"密码必须包含一个大写字母",
),
v.check((input) => /[0-9]/.test(input), "密码必须包含一个数字"),
);
这些操作修改值而不改变其类型:
字符串转换:
v.trim() — 移除首尾空白字符v.trimStart() — 移除开头空白字符v.trimEnd() — 移除结尾空白字符v.toLowerCase() — 转换为小写v.toUpperCase() — 转换为大写数字转换:
v.toMinValue(n) — 钳制到最小值(如果小于 n,则设置为 n)v.toMaxValue(n) — 钳制到最大值(如果大于 n,则设置为 n)const NormalizedEmailSchema = v.pipe(
v.string(),
v.trim(),
v.toLowerCase(),
v.email(),
);
// 将数字钳制到 0-100 范围
const PercentageSchema = v.pipe(v.number(), v.toMinValue(0), v.toMaxValue(100));
要在数据类型之间进行转换,请使用这些内置的转换操作:
v.toNumber() — 转换为数字v.toString() — 转换为字符串v.toBoolean() — 转换为布尔值v.toBigint() — 转换为大整数v.toDate() — 转换为 Date 对象// 将字符串转换为数字
const PortSchema = v.pipe(v.string(), v.toNumber(), v.integer(), v.minValue(1));
// 将 ISO 字符串转换为 Date
const TimestampSchema = v.pipe(v.string(), v.isoDateTime(), v.toDate());
// 转换为布尔值
const FlagSchema = v.pipe(v.string(), v.toBoolean());
对于自定义转换,使用 v.transform():
const DateStringSchema = v.pipe(
v.string(),
v.isoDate(),
v.transform((input) => new Date(input)),
);
// 自定义对象转换
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>;
// 常规对象 - 剥离未知键(默认)
const ObjectSchema = v.object({ key: v.string() });
// 宽松对象 - 允许并保留未知键
const LooseObjectSchema = v.looseObject({ key: v.string() });
// 严格对象 - 对未知键抛出错误
const StrictObjectSchema = v.strictObject({ key: v.string() });
// 带剩余项的对象 - 根据模式验证未知键
const ObjectWithRestSchema = v.objectWithRest(
{ key: v.string() },
v.number(), // 未知键必须是数字
);
const ProfileSchema = v.object({
// 必需
name: v.string(),
// 可选(可以是 undefined 或缺失)
nickname: v.optional(v.string()),
// 带默认值的可选
role: v.optional(v.string(), "user"),
// 可为空(可以是 null)
avatar: v.nullable(v.string()),
// 可空值(可以是 null 或 undefined)
bio: v.nullish(v.string()),
// 带默认值的可空值
theme: v.nullish(v.string(), "light"),
});
const BaseSchema = v.object({
id: v.number(),
name: v.string(),
email: v.string(),
password: v.string(),
});
// 选取特定键
const PublicUserSchema = v.pick(BaseSchema, ["id", "name"]);
// 省略特定键
const UserWithoutPasswordSchema = v.omit(BaseSchema, ["password"]);
// 使所有字段可选
const PartialUserSchema = v.partial(BaseSchema);
// 使所有字段必需
const RequiredUserSchema = v.required(PartialUserSchema);
// 合并对象
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,
"密码不匹配",
),
["confirmPassword"],
),
);
const TagsSchema = v.pipe(
v.array(v.string()),
v.minLength(1, "至少需要一个标签"),
v.maxLength(10, "最多允许 10 个标签"),
);
// 对象数组
const UsersSchema = v.array(
v.object({
id: v.number(),
name: v.string(),
}),
);
// 具有特定类型的固定长度数组
const CoordinatesSchema = v.tuple([v.number(), v.number()]);
// 类型: [number, number]
// 带剩余项的元组
const ArgsSchema = v.tupleWithRest(
[v.string()], // 第一个参数是字符串
v.number(), // 其余的是数字
);
// 类型: [string, ...number[]]
const StringOrNumberSchema = v.union([v.string(), v.number()]);
const StatusSchema = v.union([
v.literal("pending"),
v.literal("active"),
v.literal("inactive"),
]);
// 比字面量联合类型更简单
const StatusSchema = v.picklist(["pending", "active", "inactive"]);
const PrioritySchema = v.picklist([1, 2, 3]);
对于可区分联合类型,使用 variant 以获得更好的性能:
const 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); // 有效数据
} else {
console.log(result.issues); // 问题数组
}
if (v.is(EmailSchema, input)) {
// input 被类型化为 string
}
// 提前中止 - 在第一个错误处停止
v.parse(Schema, data, { abortEarly: true });
// 提前中止管道 - 在管道第一个错误处停止
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"),
});
// 输出类型(转换和默认值之后)
type User = v.InferOutput<typeof UserSchema>;
// { name: string; age: number; role: string }
// 输入类型(转换之前)
type UserInput = v.InferInput<typeof UserSchema>;
// { name: string; age: string; role?: string | undefined }
// 问题类型
type UserIssue = v.InferIssue<typeof UserSchema>;
const LoginSchema = v.object({
email: v.pipe(
v.string("邮箱必须是字符串"),
v.nonEmpty("请输入您的邮箱"),
v.email("无效的邮箱格式"),
),
password: v.pipe(
v.string("密码必须是字符串"),
v.nonEmpty("请输入您的密码"),
v.minLength(8, "密码至少需要 8 个字符"),
),
});
const result = v.safeParse(LoginSchema, data);
if (!result.success) {
const flat = v.flatten(result.issues);
// { nested: { email: ['Invalid email format'], password: ['...'] } }
}
每个问题包含:
kind: 'schema' | 'validation' | 'transformation'type: 函数名(例如 'string', 'email', 'min_length')input: 有问题的输入expected: 期望的内容received: 实际收到的内容message: 人类可读的消息path: 嵌套问题的路径项数组// 静态回退
const NumberSchema = v.fallback(v.number(), 0);
v.parse(NumberSchema, "invalid"); // 返回 0
// 动态回退
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) => {
// 检查数据库
return true;
};
const UsernameSchema = v.pipeAsync(
v.string(),
v.minLength(3),
v.checkAsync(isUsernameAvailable, "用户名已被占用"),
);
// 必须使用 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>;
// 用法
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("请输入您的邮箱"),
v.email("无效的邮箱地址"),
),
password: v.pipe(
v.string(),
v.nonEmpty("请输入您的密码"),
v.minLength(8, "密码至少需要 8 个字符"),
),
});
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);
// 字符串转 Date
const DateFromStringSchema = v.pipe(
v.string(),
v.isoDate(),
v.transform((input) => new Date(input)),
);
// 日期验证
const FutureDateSchema = v.pipe(
v.date(),
v.minValue(new Date(), "日期必须在未来"),
);
每周安装量
104
仓库
GitHub 星标数
12
首次出现
2026年2月28日
安全审计
已安装于
github-copilot101
codex100
opencode100
kimi-cli98
gemini-cli98
amp98
This skill helps you work effectively with Valibot, the modular and type-safe schema library for validating structural data.
Valibot and Zod have different APIs. Never mix them up!
| Feature | Zod ❌ | Valibot ✅ |
|---|---|---|
| Import | import { z } from 'zod' | import * as v from 'valibot' |
| Validations | Chained methods: .email().min(5) | Pipeline: v.pipe(v.string(), v.email(), v.minLength(5)) |
| Parsing | schema.parse(data) | v.parse(schema, data) |
| Safe parsing | schema.safeParse(data) | v.safeParse(schema, data) |
| Optional | z.string().optional() | v.optional(v.string()) |
| Nullable | z.string().nullable() | v.nullable(v.string()) |
| Default | z.string().default('x') | v.optional(v.string(), 'x') |
| Transform | z.string().transform(fn) | v.pipe(v.string(), v.transform(fn)) |
| Refine/Check | z.string().refine(fn) | v.pipe(v.string(), v.check(fn)) |
| Enum | z.enum(['a', 'b']) | v.picklist(['a', 'b']) |
| Native enum | z.nativeEnum(MyEnum) | v.enum(MyEnum) |
| Union | z.union([a, b]) | v.union([a, b]) |
| Discriminated union | z.discriminatedUnion('type', [...]) | v.variant('type', [...]) |
| Intersection | z.intersection(a, b) | v.intersect([a, b]) |
| Min/max length | .min(5).max(10) | v.minLength(5), v.maxLength(10) |
| Min/max value | .gte(5).lte(10) | v.minValue(5), v.maxValue(10) |
| Infer type | z.infer<typeof Schema> | v.InferOutput<typeof Schema> |
| Infer input | z.input<typeof Schema> | v.InferInput<typeof Schema> |
// ❌ 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 # bun
Import with a wildcard (recommended):
import * as v from "valibot";
Or with individual imports:
import { object, string, pipe, email, parse } from "valibot";
Valibot's API is divided into three main concepts:
Schemas define the expected data type. They are the starting point.
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(),
});
Methods help you use or modify schemas. The schema is always the first argument.
// 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
}
Actions validate or transform data within a pipe(). They MUST be used inside pipelines.
// Actions are used in pipe()
const EmailSchema = v.pipe(
v.string(),
v.trim(),
v.email(),
v.endsWith("@example.com"),
);
Pipelines extend schemas with validation and transformation actions. A pipeline always starts with a schema, followed by actions.
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"),
);
String validations:
v.email() — Valid email formatv.url() — Valid URL formatv.uuid() — Valid UUID formatv.regex(pattern) — Match regex patternv.minLength(n) — Minimum lengthv.maxLength(n) — Maximum lengthv.length(n) — Exact lengthv.nonEmpty() — Not empty stringv.startsWith(str) — Starts with stringv.endsWith(str) — Ends with stringNumber validations:
v.minValue(n) — Minimum value (>=)v.maxValue(n) — Maximum value (<=)v.gtValue(n) — Greater than (>)v.ltValue(n) — Less than (<)v.integer() — Must be integerv.finite() — Must be finitev.safeInteger() — Safe integer rangev.multipleOf(n) — Must be multiple of nArray validations:
v.minLength(n) — Minimum itemsv.maxLength(n) — Maximum itemsv.length(n) — Exact item countv.nonEmpty() — At least one itemv.includes(item) — Contains itemv.excludes(item) — Does not contain itemconst 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"),
);
These actions modify the value without changing its type:
String transformations:
v.trim() — Remove leading/trailing whitespacev.trimStart() — Remove leading whitespacev.trimEnd() — Remove trailing whitespacev.toLowerCase() — Convert to lowercasev.toUpperCase() — Convert to uppercaseNumber transformations:
v.toMinValue(n) — Clamp to minimum value (if less than n, set to n)
v.toMaxValue(n) — Clamp to maximum value (if greater than n, set to 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));
For converting between data types, use these built-in transformation actions:
v.toNumber() — Convert to number
v.toString() — Convert to string
v.toBoolean() — Convert to boolean
v.toBigint() — Convert to bigint
v.toDate() — Convert to Date
// 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());
For custom transformations, use 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]);
Use variant for better performance with discriminated unions:
const 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: ['...'] } }
}
Each issue contains:
kind: 'schema' | 'validation' | 'transformation'type: Function name (e.g., 'string', 'email', 'min_length')input: The problematic inputexpected: What was expectedreceived: What was receivedmessage: Human-readable messagepath: Array of path items for nested issues// 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)),
});
For async operations (e.g., database checks), use async variants:
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"),
);
Weekly Installs
104
Repository
GitHub Stars
12
First Seen
Feb 28, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
github-copilot101
codex100
opencode100
kimi-cli98
gemini-cli98
amp98
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
118,000 周安装
v.includes(str) — Contains string