Dead simple Object schema validation
Comprehensive validation system with async/sync validation, custom tests, conditional validation, and detailed error handling for robust data validation scenarios.
Primary methods for validating data against schemas, available on all schema types.
/**
* Asynchronously validate a value against the schema
* @param value - Value to validate
* @param options - Validation configuration options
* @returns Promise resolving to validated and transformed value
* @throws ValidationError if validation fails
*/
validate(value: any, options?: ValidateOptions): Promise<T>;
/**
* Synchronously validate a value against the schema
* @param value - Value to validate
* @param options - Validation configuration options
* @returns Validated and transformed value
* @throws ValidationError if validation fails
*/
validateSync(value: any, options?: ValidateOptions): T;
/**
* Validate a nested field at a specific path within an object
* @param path - Dot-notation path to the field
* @param value - Root object containing the field
* @param options - Validation configuration options
* @returns Promise resolving to validated field value
*/
validateAt(path: string, value: any, options?: ValidateOptions): Promise<any>;
/**
* Synchronously validate a nested field at a specific path
* @param path - Dot-notation path to the field
* @param value - Root object containing the field
* @param options - Validation configuration options
* @returns Validated field value
*/
validateSyncAt(path: string, value: any, options?: ValidateOptions): any;
/**
* Check if a value is valid without throwing errors
* @param value - Value to check
* @param options - Validation configuration options
* @returns Promise resolving to boolean validity status
*/
isValid(value: any, options?: ValidateOptions): Promise<boolean>;
/**
* Synchronously check if a value is valid without throwing errors
* @param value - Value to check
* @param options - Validation configuration options
* @returns Boolean validity status
*/
isValidSync(value: any, options?: ValidateOptions): boolean;
/**
* Check if a value matches the schema's expected type
* @param value - Value to type-check
* @returns Type predicate indicating if value matches schema type
*/
isType(value: any): value is T;
interface ValidateOptions<TContext = {}> {
/** Enable strict validation (no type coercion) */
strict?: boolean;
/** Stop validation on first error (default: true) */
abortEarly?: boolean;
/** Remove unknown fields from objects */
stripUnknown?: boolean;
/** Validate nested schemas recursively */
recursive?: boolean;
/** Additional context data available during validation */
context?: TContext;
}Usage Examples:
import { object, string, number } from "yup";
const userSchema = object({
name: string().required(),
age: number().positive().integer(),
});
// Async validation
try {
const user = await userSchema.validate({
name: "John",
age: 25
});
console.log(user); // { name: "John", age: 25 }
} catch (error) {
console.error(error.errors); // Array of error messages
}
// Sync validation
try {
const user = userSchema.validateSync({ name: "John", age: 25 });
} catch (error) {
console.error(error.message);
}
// Check validity without throwing
const isValid = await userSchema.isValid({ name: "John", age: -5 }); // false
// Validate nested field
await userSchema.validateAt("name", { name: "John", age: 25 }); // "John"Transform and coerce values to match schema types without full validation.
/**
* Cast/transform a value to match the schema type
* @param value - Value to cast
* @param options - Casting configuration options
* @returns Transformed value matching schema type
*/
cast(value: any, options?: CastOptions): T;
interface CastOptions<TContext = {}> {
/** Enable strict casting (minimal transformations) */
strict?: boolean;
/** Remove unknown fields from objects */
stripUnknown?: boolean;
/** Additional context data available during casting */
context?: TContext;
}Usage Examples:
import { string, number, date } from "yup";
// String casting
const stringSchema = string();
stringSchema.cast(123); // "123"
stringSchema.cast(true); // "true"
// Number casting
const numberSchema = number();
numberSchema.cast("42"); // 42
numberSchema.cast("3.14"); // 3.14
// Date casting
const dateSchema = date();
dateSchema.cast("2023-01-01"); // Date object
dateSchema.cast(1640995200000); // Date from timestampAdd custom validation logic with detailed error reporting and context access.
/**
* Add a custom validation test to the schema
* @param name - Test name for error reporting
* @param message - Error message when test fails
* @param testFn - Function that returns true if valid
* @returns New schema with custom test
*/
test(name: string, message: string, testFn: TestFunction): Schema;
/**
* Add a custom validation test with full configuration
* @param options - Complete test configuration
* @returns New schema with custom test
*/
test(options: TestConfig): Schema;
type TestFunction<T = any, C = any> = (
value: T,
context: TestContext<T, C>
) => boolean | Promise<boolean>;
interface TestConfig<T = any, C = any> {
/** Unique name for the test */
name: string;
/** Error message when test fails (string or function) */
message: string | ((params: TestMessageParams) => string);
/** Test function that validates the value */
test: TestFunction<T, C>;
/** Skip test if value is empty/null/undefined */
skipAbsent?: boolean;
/** Test is exclusive - removes other tests with same name */
exclusive?: boolean;
/** Parameters to pass to error message function */
params?: Record<string, any>;
}
interface TestContext<T = any, C = any> {
/** Path to current field being validated */
path: string;
/** Field name being validated */
key?: string;
/** Parent object containing the field */
parent: any;
/** Root value being validated */
from: Array<{ schema: Schema; value: any }>;
/** Additional context data */
options: ValidateOptions<C>;
/** Create ValidationError for this test */
createError(params?: CreateErrorOptions): ValidationError;
/** Resolve references and lazy schemas */
resolve(value: any): any;
}
interface CreateErrorOptions {
/** Override default error message */
message?: string;
/** Override field path */
path?: string;
/** Override field value */
value?: any;
/** Additional parameters for message interpolation */
params?: Record<string, any>;
}Usage Examples:
import { string, number, object } from "yup";
// Simple custom test
const evenNumberSchema = number().test(
"is-even",
"Number must be even",
(value) => value % 2 === 0
);
// Async custom test with API call
const uniqueEmailSchema = string().test(
"unique-email",
"Email already exists",
async (email) => {
const exists = await checkEmailExists(email);
return !exists;
}
);
// Custom test with context access
const passwordSchema = object({
password: string().min(8),
confirmPassword: string().test(
"passwords-match",
"Passwords must match",
function(value) {
return value === this.parent.password;
}
),
});
// Custom test with dynamic message
const minLengthSchema = string().test({
name: "min-length",
message: ({ min }) => `Must be at least ${min} characters`,
params: { min: 5 },
test: (value) => value && value.length >= 5,
});Create dynamic validation rules based on other field values or conditions.
/**
* Apply conditional validation based on other fields
* @param keys - Field name(s) to check for condition
* @param builder - Function or config that returns schema based on condition
* @returns New schema with conditional validation
*/
when<U extends Schema>(
keys: string | string[],
builder: WhenBuilder<U> | WhenBuilderOptions<U>
): Schema;
type WhenBuilder<U extends Schema> = (
value: any,
schema: Schema
) => U;
interface WhenBuilderOptions<U extends Schema> {
/** Value(s) to match against */
is?: any | any[];
/** Schema to use when condition matches */
then?: U | ((schema: Schema) => U);
/** Schema to use when condition doesn't match */
otherwise?: U | ((schema: Schema) => U);
}Usage Examples:
import { object, string, boolean, number } from "yup";
// Simple conditional validation
const schema = object({
isBusiness: boolean(),
companyName: string().when("isBusiness", {
is: true,
then: (schema) => schema.required("Company name is required"),
otherwise: (schema) => schema.strip(),
}),
});
// Multiple conditions
const subscriptionSchema = object({
type: string().oneOf(["free", "premium", "enterprise"]),
seats: number().when("type", {
is: "enterprise",
then: (schema) => schema.min(10).required(),
otherwise: (schema) => schema.max(5),
}),
customDomain: string().when("type", (type, schema) => {
return type === "premium" || type === "enterprise"
? schema.required()
: schema.strip();
}),
});
// Multiple field dependencies
const addressSchema = object({
country: string().required(),
state: string().when("country", {
is: "US",
then: (schema) => schema.required(),
}),
zipCode: string().when(["country", "state"], {
is: (country, state) => country === "US" && state,
then: (schema) => schema.matches(/^\d{5}$/, "Invalid ZIP code"),
}),
});Comprehensive error information with detailed validation failure reporting.
/**
* Error thrown when validation fails
*/
class ValidationError extends Error {
/** Always "ValidationError" */
name: "ValidationError";
/** Primary error message */
message: string;
/** Value that failed validation */
value: any;
/** Path where validation failed */
path?: string;
/** Type of validation that failed */
type?: string;
/** Parameters used in error message */
params?: Record<string, any>;
/** Array of all error messages */
errors: string[];
/** Array of nested ValidationError objects */
inner: ValidationError[];
/**
* Format error message with parameters
* @param message - Message template
* @param params - Parameters for interpolation
* @returns Formatted message
*/
static formatError(
message: string | ((params: any) => string),
params: Record<string, any>
): string;
/**
* Check if an error is a ValidationError
* @param err - Error to check
* @returns Type predicate for ValidationError
*/
static isError(err: any): err is ValidationError;
}Usage Examples:
import { object, string, ValidationError } from "yup";
const schema = object({
name: string().required("Name is required"),
email: string().email("Invalid email format").required(),
});
try {
await schema.validate({ name: "", email: "not-email" });
} catch (error) {
if (ValidationError.isError(error)) {
console.log("Validation failed!");
console.log("Primary message:", error.message);
console.log("All errors:", error.errors);
console.log("Failed at path:", error.path);
console.log("Failed value:", error.value);
// Access nested errors
error.inner.forEach((nestedError) => {
console.log(`${nestedError.path}: ${nestedError.message}`);
});
}
}
// Custom error handling
const handleValidationError = (error: ValidationError) => {
const fieldErrors: Record<string, string> = {};
error.inner.forEach((err) => {
if (err.path) {
fieldErrors[err.path] = err.message;
}
});
return fieldErrors;
};Apply custom transformations to values during validation and casting.
/**
* Add a transformation function to the schema
* @param transformFn - Function to transform the value
* @returns New schema with transformation applied
*/
transform<U>(transformFn: TransformFunction<T, U>): Schema<U>;
type TransformFunction<T, U> = (
currentValue: T,
originalValue: any,
context: TransformContext
) => U;
interface TransformContext {
/** Path to current field */
path: string;
/** Parent object */
parent: any;
/** Root value being transformed */
root: any;
/** Additional context data */
options: CastOptions;
}Usage Examples:
import { string, number, array } from "yup";
// String transformation
const slugSchema = string()
.transform((value) => value?.toLowerCase().replace(/\s+/g, "-"))
.matches(/^[a-z0-9-]+$/, "Invalid slug format");
// Number transformation
const currencySchema = string()
.transform((value) => {
// Remove currency symbols and convert to number
const cleaned = value?.replace(/[$,]/g, "");
return parseFloat(cleaned) || 0;
})
.transform((value) => Math.round(value * 100) / 100); // Round to 2 decimals
// Array transformation
const csvToArraySchema = string()
.transform((value) => value?.split(",").map(s => s.trim()))
.transform((arr) => arr?.filter(Boolean)); // Remove empty strings
// Context-aware transformation
const fullNameSchema = object({
firstName: string().required(),
lastName: string().required(),
fullName: string().transform(function(value, originalValue, context) {
// Auto-generate full name if not provided
if (!value && context.parent) {
return `${context.parent.firstName} ${context.parent.lastName}`;
}
return value;
}),
});Add metadata and labels to schemas for enhanced error reporting and documentation.
/**
* Set or get metadata for the schema
* @param metadata - Metadata object to set (optional)
* @returns Schema metadata or new schema with metadata
*/
meta(): Record<string, any> | undefined;
meta(metadata: Record<string, any>): Schema;
/**
* Set a human-readable label for better error messages
* @param label - Human-readable field label
* @returns New schema with label
*/
label(label: string): Schema;
/**
* Set custom error message for type validation failures
* @param message - Custom type error message
* @returns New schema with custom type error
*/
typeError(message: string): Schema;
/**
* Get a description of the schema structure and rules
* @param options - Description options
* @returns Schema description object
*/
describe(options?: DescribeOptions): SchemaDescription;
interface DescribeOptions {
/** Include field values in description */
value?: any;
/** Additional context */
context?: any;
}
interface SchemaDescription {
/** Schema type */
type: string;
/** Human-readable label */
label?: string;
/** Schema metadata */
meta?: Record<string, any>;
/** Whether field is required */
optional: boolean;
/** Whether field is nullable */
nullable: boolean;
/** Default value */
default?: any;
/** Applied tests and validations */
tests: Array<{ name: string; params?: any }>;
/** Inner type for arrays/objects */
innerType?: SchemaDescription;
/** Object fields */
fields?: Record<string, SchemaDescription>;
}Usage Examples:
import { string, object } from "yup";
// Schema with labels and metadata
const userSchema = object({
email: string()
.email()
.required()
.label("Email Address")
.meta({
category: "contact",
sensitive: true,
helpText: "We'll use this to send you updates"
}),
username: string()
.matches(/^[a-zA-Z0-9_]+$/)
.required()
.label("Username")
.typeError("Username must be a string")
.meta({ category: "identity" }),
});
// Access metadata
const emailMeta = userSchema.fields.email.meta();
console.log(emailMeta.helpText); // "We'll use this to send you updates"
// Schema description
const description = userSchema.describe();
console.log(description.fields.email.label); // "Email Address"
console.log(description.fields.email.tests); // Array of validation testsInstall with Tessl CLI
npx tessl i tessl/npm-yup