API
One page, every method. Copy the line you need.
Primitives
v.string()
v.string().min(3).max(50).length(10)
v.string().email().url().uuid()
v.string().trim().lowercase().uppercase()
v.string().startsWith("api_").endsWith("_v1").includes("foo")
v.string().regex(/^\d+$/)
v.string().alpha() // letters only (a-z, A-Z, Unicode)
v.string().numeric() // digits only (0-9)
v.string().symbol() // non-alphanumeric only
v.string().phone() // phone number
v.string().e164() // strict E.164 (+6281234567890)
v.string().jwt() // three base64url segments
v.string().mac() // AA:BB:CC:DD:EE:FF
v.string().semver() // 1.2.3 with prerelease/build
v.string().creditCard() // Luhn check
v.string().imei() // 15-digit IMEI with Luhn check
v.string().hash("sha256") // hex digest (md5/sha1/sha256/sha512)
v.string().hex() // hex with optional 0x
v.string().base64url() // base64url-encoded
v.string().lowercaseCheck() // must be lowercase
v.string().uppercaseCheck() // must be uppercase
v.string().normalized() // must be NFC-normalized
v.string().ip() // IPv4/IPv6
v.string().ip(4) // IPv4 only
v.string().cidr()
v.string().base64()
v.string().cuid().cuid2().ulid().nanoid().emoji()
v.string().datetime().date().time()
v.string().nonempty() // 1 char or more
v.number().min(0).max(100).int().positive().multipleOf(5)
v.number().lt(50).gt(0).safe().finite()
v.number().nan() // accept NaN (rejected by default)
v.number().step(5) // multiple of 5 from 0
v.boolean()
v.date().min(new Date("2024-01-01")).max(new Date("2024-12-31"))
v.bigint().min(0n).max(100n)
v.file().maxSize(1048576).mime("image/png")
v.templateLiteral(["Hello, ", v.string().min(1), "!"])
v.custom((x): x is string => typeof x === "string")
v.literal("admin")
v.enum(["a", "b", "c"])
v.nativeEnum(MyEnum)Objects
const User = v.object({
name: v.string(),
age: v.number().optional(),
role: v.enum(["admin", "user"]).default("user"),
});
User.partial() // all fields optional
User.required() // all fields required
User.required(["name"]) // only these required
User.pick(["name"]) // { name: string }
User.omit(["age"])
User.strict() // rejects unknown keys
User.passthrough() // keeps unknown keys
User.catchall(v.string()) // unknown keys validated as string
User.keyof() // enum of shape keys
User.readonly() // type-level Readonly
User.extend({ role: v.string() })
User.merge(other)Collections, unions, modifiers
v.array(v.string()).min(1).max(10)
v.array(v.string()).nonempty()
v.array(v.string()).unique()
v.tuple(v.string(), v.number())
v.set(v.string())
v.map(v.string(), v.number())
v.union([v.string(), v.number()])
v.string().or(v.number())
v.intersection(A, B) // A & B
v.intersection(A, B, C) // variadic
A.and(B).and(C)
v.string().optional() // string | undefined
v.string().nullable() // string | null
v.string().nullish() // string | null | undefined
v.string().default("n/a")
v.string().catch("fallback")
v.string().brand("UserId")
v.string().readonly()Validation flow
v.string().refine(s => s.length > 0, "Must be non-empty")
v.string().superRefine((val, ctx) => {
if (val.length < 8) ctx.addIssue({ code: "custom", message: "Min 8 chars" });
})
v.string().transform(s => parseInt(s))
v.string().trim().lowercase().pipe(v.string().min(1))
v.coerce.number() // input → Number(input)
v.preprocess(x => JSON.parse(x), v.object({ x: v.number() }))
v.lazy(() => Category) // recursive schemas
schema.safeParse(input) // { success, data } or { success, errors }
schema.parse(input) // data or throws ValdixError
await schema.safeParseAsync(input)
schema["~standard"]() // Standard Schema interop
schema.toJSONSchema() // JSON Schema exportErrors
v.string().min(3, "Minimal 3 karakter ya")
v.object({
userName: v.string().min(3, "{{field}} minimal 3 karakter"),
})
// → "user name minimal 3 karakter"
v.setErrorMap((issue, ctx) => `${issue.code}: ${ctx.defaultError}`)
v.string().email().describe("Email address")
// → issue.description === "Email address"
v.registerLocale("fr", { required: () => "Ce champ est requis" })