- name
- valibot
- description
- 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.
- license
- MIT
- metadata
- {"author":"open-circle","version":"1.0"}
# Valibot
This skill helps you work effectively with [Valibot](https://valibot.dev), the modular and type-safe schema library for validating structural data.
## When to use this skill
- When the user asks about schema validation with Valibot
- When creating or modifying Valibot schemas
- When parsing or validating user input
- When the user mentions Valibot, schema, or validation
- When migrating from Zod to Valibot
## CRITICAL: Valibot vs Zod — Do Not Confuse!
**Valibot and Zod have different APIs. Never mix them up!**
### Key Differences
| 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>` |
### Common Mistakes to Avoid
```typescript
// ❌ 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);
```
```typescript
// ❌ 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()),
});
```
```typescript
// ❌ WRONG - Zod-style default
const Schema = v.string().default("hello");
// ✅ CORRECT - Valibot uses second argument
const Schema = v.optional(v.string(), "hello");
```
## Installation
```bash
npm install valibot # npm
yarn add valibot # yarn
pnpm add valibot # pnpm
bun add valibot # bun
```
Import with a wildcard (recommended):
```typescript
import * as v from "valibot";
```
Or with individual imports:
```typescript
import { object, string, pipe, email, parse } from "valibot";
```
## Mental Model
Valibot's API is divided into three main concepts:
### 1. Schemas
Schemas define the expected data type. They are the starting point.
```typescript
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(),
});
```
### 2. Methods
Methods help you use or modify schemas. The schema is always the first argument.
```typescript
// 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
}
```
### 3. Actions
Actions validate or transform data within a `pipe()`. They MUST be used inside pipelines.
```typescript
// Actions are used in pipe()
const EmailSchema = v.pipe(
v.string(),
v.trim(),
v.email(),
v.endsWith("@example.com"),
);
```
## Pipelines
Pipelines extend schemas with validation and transformation actions. A pipeline always starts with a schema, followed by actions.
```typescript
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"),
);
```
### Common Validation Actions
**String validations:**
- `v.email()` — Valid email format
- `v.url()` — Valid URL format
- `v.uuid()` — Valid UUID format
- `v.regex(pattern)` — Match regex pattern
- `v.minLength(n)` — Minimum length
- `v.maxLength(n)` — Maximum length
- `v.length(n)` — Exact length
- `v.nonEmpty()` — Not empty string
- `v.startsWith(str)` — Starts with string
- `v.endsWith(str)` — Ends with string
- `v.includes(str)` — Contains string
**Number 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 integer
- `v.finite()` — Must be finite
- `v.safeInteger()` — Safe integer range
- `v.multipleOf(n)` — Must be multiple of n
**Array validations:**
- `v.minLength(n)` — Minimum items
- `v.maxLength(n)` — Maximum items
- `v.length(n)` — Exact item count
- `v.nonEmpty()` — At least one item
- `v.includes(item)` — Contains item
- `v.excludes(item)` — Does not contain item
### Custom Validation with check()
```typescript
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"),
);
```
### Value Transformations
These actions modify the value without changing its type:
**String transformations:**
- `v.trim()` — Remove leading/trailing whitespace
- `v.trimStart()` — Remove leading whitespace
- `v.trimEnd()` — Remove trailing whitespace
- `v.toLowerCase()` — Convert to lowercase
- `v.toUpperCase()` — Convert to uppercase
**Number 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)
```typescript
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));
```
### Type Transformations
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
```typescript
// 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());
```
### Custom Transformations
For custom transformations, use `v.transform()`:
```typescript
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}`,
})),
);
```
## Object Schemas
### Basic Object
```typescript
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>;
```
### Object Variants
```typescript
// 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
);
```
### Optional and Nullable Fields
```typescript
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"),
Ver en GitHub