Package Entry Point
| Import | Purpose |
|---|---|
@vielzeug/validit | Main exports and types |
API At a Glance
| Symbol | Purpose | Execution mode | Common gotcha |
|---|---|---|---|
v.object() | Create typed object schema definitions | Sync | Unknown keys are rejected by default |
safeParse() | Validate unknown input without throwing | Sync | Always branch on success before using parsed data |
safeParseAsync() | Validate async check pipelines | Async | Await result to capture async validation issues |
configure() | Override global validation messages | Sync | Message keys are nested groups, not flat string keys |
Most Used First
External boundaries
- Use
safeParse()when validating request payloads, form input, query params, or external API responses. - Use
safeParseAsync()when any part of the schema tree uses asynccheck()validation. - Use
parse()andparseAsync()when validation failure should be exceptional and you want a thrownValidationError.
Common composition flow
- Start with a factory from
v. - Add built-in constraints like
.min()or.email(). - Add
preprocess(),default(),optional(), ornullable()as needed. - Add
.check()for domain-specific rules. - Add
transform()only after type-specific validators are complete.
Package Exports
import {
configure,
ErrorCode,
resolveMessage,
prependIssuePath,
toJsonSchema,
Schema,
ValidationError,
reset,
v,
type CheckContext,
type CheckFnResult,
type JsonSchema,
type Infer,
type InferInput,
type InferOutput,
type Issue,
type MessageFn,
type ParseResult,
type SchemaMeta,
type SchemaConstraints,
type SchemaTypeHint,
type StringConstraints,
type NumberConstraints,
type ArrayConstraints,
type ValidateFn,
type Messages,
} from '@vielzeug/validit';@vielzeug/validit exports v as the canonical schema factory namespace.
v Namespace
Primitive Factories
v.any()v.unknown()v.string()v.number()v.boolean()v.date()v.literal(value)v.enum(values)v.nativeEnum(enumObj)v.null()v.undefined()v.never()
Composite Factories
v.object(shape)v.array(itemSchema)v.set(itemSchema)v.map(keySchema, valueSchema)v.tuple(items)v.record(keySchema, valueSchema)v.union(a, b, ...rest)v.intersect(a, b, ...rest)v.variant(discriminator, map)v.lazy(getter)v.instanceof(ClassCtor)
Coercion Factories
v.coerce.string()v.coerce.number()v.coerce.boolean()v.coerce.date()v.coerce.bigint()
Utility Factories
v.bigint()
Schema
All schema classes inherit from Schema<Output, Input, Constraints, TypeHint>.
Validation
parse(value: unknown): Output
safeParse(value: unknown): ParseResult<Output>
parseAsync(value: unknown): Promise<Output>
safeParseAsync(value: unknown): Promise<ParseResult<Output>>parse()throwsValidationErroron failure.safeParse()never throws for validation failures.- If async validators exist,
parse()throws and you must useparseAsync()orsafeParseAsync().
parse() can throw either:
ValidationErrorfor validation failuresErrorwhen called on schemas that contain async validators
Modifiers
optional(): Schema<Output | undefined>
nullable(): Schema<Output | null>
nullish(): Schema<Output | null | undefined>
required(): Schema<Exclude<Output, undefined>>
default(value: Output | (() => Output)): this
catch(fallback: Output | (() => Output)): thisdefault()applies only when the input isundefined.catch()returns a fallback only forValidationErrorfailures.
Custom Validation
check(
fn: (
value: Output,
ctx: CheckContext,
) => CheckFnResult | Promise<CheckFnResult>,
): thischeck()supports sync and async validation.- Return
falseto use the default message, return anIssueorIssue[]for custom issues, or usectx.addIssue()for explicit path control.
Transform and Metadata
transform<NewOutput>(fn: (value: Output) => NewOutput): Schema<NewOutput, Input>
preprocess(fn: (value: unknown) => unknown): this
describe(description: string): this
readonly description: string | undefined
brand<Brand extends string>(): Schema<Output & { __brand: Brand }, Input>
readonly(): Schema<Readonly<Output>, Input>
is(value: unknown): value is Outputpreprocess() steps run in declaration order.
StringSchema
v.string() methods:
.min(length, message?).max(length, message?).length(exact, message?).nonEmpty(message?).startsWith(prefix, message?).endsWith(suffix, message?).includes(substr, message?).regex(pattern, message?).email(message?).url(message?).uuid(message?).isoDate(message?).isoDateTime(message?).ip(message?).trim().lowercase().uppercase().cuid().cuid2().ulid().nanoid().base64().base64url().hex().hexColor().emoji().jwt().time().duration().semver().slug().numeric()
Notes:
email()uses a pragmatic syntax-focused regex, not full SMTP validation.ip()accepts IPv4 and IPv6.
NumberSchema
v.number() methods:
.min(min, message?).max(max, message?).int(message?).positive(message?).negative(message?).nonNegative(message?).nonPositive(message?).multipleOf(step, message?).safe(message?).finite(message?)
safe() checks Number.isSafeInteger(value).
DateSchema
v.date() methods:
.min(date, message?).max(date, message?)
v.coerce.date() converts string or number inputs to Date before validation.
ArraySchema
v.array(schema) methods:
.min(length, message?).max(length, message?).length(exact, message?).nonEmpty(message?).unique(message?)
unique() uses JavaScript Set semantics.
ObjectSchema
v.object(shape) methods:
.partial().partial(...keys).required().extend(extraShape).pick(...keys).omit(...keys).relaxed().strip()
Properties:
.shape(readonly)
Behavior notes:
v.object(...)rejects unknown keys by default..relaxed()allows unknown keys and preserves them in output..strip()ignores unknown keys in output.
TupleSchema
v.tuple(items) validates fixed-length arrays with per-index schemas.
- Input must be an array.
- Input length must match the tuple length exactly by default.
.rest(schema)allows variadic tail items validated byschema.
RecordSchema
v.record(keySchema, valueSchema) validates object keys and values.
- The input must be a plain object.
- Keys are parsed as strings through
keySchema. - If the parsed key changes, the output uses the parsed key.
Example:
v.record(v.string().trim().lowercase(), v.string()).parse({ ' X-ID ': '1' });
// => { 'x-id': '1' }UnionSchema
v.union(a, b, ...rest):
- accepts schema instances or raw literal values (
string | number | boolean | null | undefined) - returns the output of the first successful branch
- exposes
.schemas(readonly normalized branch schemas)
IntersectSchema
v.intersect(a, b, ...rest):
- all branches must pass
- aggregates issues from failing branches
- merges object outputs from successful branches in order
- exposes
.schemas(readonly)
VariantSchema
v.variant(discriminator, map):
- requires
mapvalues to be object schemas - injects discriminator literals into each branch internally
- validates in O(1) by discriminator lookup
- inherits strict object behavior from each branch unless the branch schema uses
.relaxed()
Other Schemas
v.lazy(getter)for recursive schemasv.instanceof(Ctor)for class instance checksv.bigint()for bigint values and constraintsv.set(itemSchema)for typed setsv.map(keySchema, valueSchema)for typed mapsv.enum(values)for string or number tuplesv.nativeEnum(enumObj)for TS native enumsv.literal(value)for exact value matchv.never()always fails
Global Messages
configure()
configure({
messages: {
number: {
min: ({ min }) => `Must be >= ${min}`,
},
string: {
email: () => 'Invalid email',
ip: () => 'Invalid IP address',
},
},
});reset()
reset();Messages
Messages is the full nested message contract used internally. configure({ messages }) accepts a deep partial shape, so you can override only the keys you need.
Types
Infer and InferInput
type User = Infer<typeof UserSchema>;
type UserIn = InferInput<typeof UserSchema>;InferInput<T> extracts the accepted input type, while Infer<T> extracts output types.
ParseResult
type ParseResult<T> = { success: true; data: T } | { success: false; error: ValidationError };MessageFn
type MessageFn<Ctx extends Record<string, unknown> = Record<string, unknown>> = string | ((ctx: Ctx) => string);Metadata Exports
Validit exposes low-level schema metadata for advanced tooling:
SchemaMetaSchemaConstraintsStringConstraintsNumberConstraintsArrayConstraintsSchemaTypeHintschema.meta
These metadata contracts are experimental and may evolve in minor versions. For stable integrations, prefer toJsonSchema() output.
Issue
type Issue = {
code: ErrorCode | (string & {});
message: string;
params?: Record<string, unknown>;
path: (string | number)[];
};Errors
ValidationError
.issues: Issue[].flatten(): { fieldErrors: Record<string, string[]>; formErrors: string[] }.format(): { _errors: string[]; [key: string]: ... }ValidationError.is(value): value is ValidationError
Use ValidationError.flattenFirst() when you need one message per field.
ErrorCode
Built-in codes:
custominvalid_base64invalid_bigintinvalid_dateinvalid_durationinvalid_enuminvalid_finiteinvalid_integerinvalid_keysinvalid_lengthinvalid_literalinvalid_multiple_ofinvalid_safeinvalid_stringinvalid_typeinvalid_unioninvalid_uniqueinvalid_urlinvalid_varianttoo_bigtoo_smallinvalid_keys