Claude Code Plugins

Community-maintained marketplace

Feedback

Validates data with Valibot's modular, tree-shakable schema library for minimal bundle size. Use when bundle size matters, building form validation, or needing lightweight TypeScript validation.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name valibot
description Validates data with Valibot's modular, tree-shakable schema library for minimal bundle size. Use when bundle size matters, building form validation, or needing lightweight TypeScript validation.

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.