Dead simple Object schema validation
Utility functions and advanced features for schema manipulation, references, lazy evaluation, and customization including localization and custom method extension.
Functions for navigating and extracting nested schemas from complex object structures.
/**
* Navigate to a nested schema at a specific path
* @param schema - Root schema to navigate from
* @param path - Dot-notation path to target schema
* @param value - Optional value for context-dependent schemas
* @param context - Optional validation context
* @returns Schema at the specified path
* @throws Error if path is not found
*/
function reach(
schema: Schema,
path: string,
value?: any,
context?: any
): Schema;
/**
* Get nested schema and context information at path
* @param schema - Root schema to navigate from
* @param path - Dot-notation path to target schema
* @param value - Optional value for context-dependent schemas
* @param context - Optional validation context
* @returns Object containing schema and context at path
*/
function getIn(
schema: Schema,
path: string,
value?: any,
context?: any
): {
schema: Schema;
parent: any;
parentPath: string;
};Usage Examples:
import { object, string, array, reach, getIn } from "yup";
const userSchema = object({
profile: object({
name: string().required(),
addresses: array(object({
street: string().required(),
city: string().required(),
})),
}),
});
// Navigate to nested schema
const nameSchema = reach(userSchema, "profile.name");
const addressSchema = reach(userSchema, "profile.addresses[0]");
const citySchema = reach(userSchema, "profile.addresses[0].city");
// Validate using reached schema
await nameSchema.validate("John Doe"); // "John Doe"
// Get schema with context
const result = getIn(userSchema, "profile.addresses[0].city");
console.log(result.schema); // StringSchema for city
console.log(result.parentPath); // "profile.addresses[0]"Utility for identifying yup schema objects.
/**
* Check if a value is a yup schema
* @param value - Value to test
* @returns Type predicate indicating if value is a Schema
*/
function isSchema(value: any): value is Schema;Usage Examples:
import { string, number, isSchema } from "yup";
const stringSchema = string();
const numberValue = 42;
const plainObject = { type: "string" };
console.log(isSchema(stringSchema)); // true
console.log(isSchema(numberValue)); // false
console.log(isSchema(plainObject)); // false
// Use in conditional logic
function processInput(input: any) {
if (isSchema(input)) {
return input.describe();
} else {
return { value: input };
}
}Create references to other fields in the same validation context for cross-field validation and dependencies.
/**
* Create a reference to another field in the validation context
* @param path - Path to the referenced field
* @param options - Reference configuration options
* @returns Reference object pointing to the specified path
*/
function ref(path: string, options?: ReferenceOptions): Reference;
interface ReferenceOptions {
/** Transform the referenced value before using it */
map?: (value: any) => any;
}
/**
* Reference class for cross-field dependencies
*/
class Reference {
/** Path being referenced */
key: string;
/** Whether reference points to validation context ($) */
isContext: boolean;
/** Whether reference points to current value (.) */
isValue: boolean;
/** Whether reference points to sibling field */
isSibling: boolean;
/** Parsed path segments */
path: string[];
/** Function to extract property value */
getter: (data: any) => any;
/** Optional value transformation function */
map?: (value: any) => any;
/**
* Get the referenced value from provided data
* @param value - Current field value
* @param parent - Parent object containing fields
* @param context - Validation context
* @returns Referenced value
*/
getValue(value?: any, parent?: any, context?: any): any;
/**
* Cast the referenced value
* @param value - Value to cast
* @param options - Cast options
* @returns Cast value
*/
cast(value: any, options?: CastOptions): any;
/**
* Resolve the reference (returns self)
* @returns This reference instance
*/
resolve(): Reference;
/**
* Get a description of the reference
* @returns Reference description object
*/
describe(): { type: "ref"; key: string };
/**
* String representation of the reference
* @returns String representation
*/
toString(): string;
/**
* Check if a value is a Reference
* @param value - Value to test
* @returns Type predicate for Reference
*/
static isRef(value: any): value is Reference;
}Usage Examples:
import { object, string, number, ref } from "yup";
// Basic reference usage
const schema = object({
password: string().min(8).required(),
confirmPassword: string()
.oneOf([ref("password")], "Passwords must match")
.required(),
});
// Reference with transformation
const priceSchema = object({
price: number().positive().required(),
discountedPrice: number()
.max(ref("price", {
map: (price) => price * 0.9 // 10% max discount
}), "Discount cannot exceed 10%")
.positive(),
});
// Context references (access validation context)
const contextSchema = object({
userRole: string().required(),
adminAction: string().when("$isAdmin", {
is: true,
then: (schema) => schema.required(),
otherwise: (schema) => schema.strip(),
}),
});
await contextSchema.validate(
{ userRole: "user", adminAction: "delete" },
{ context: { isAdmin: false } }
);
// Complex reference paths
const nestedSchema = object({
users: array(object({
name: string().required(),
email: string().email().required(),
})),
primaryUser: object({
name: string().oneOf([ref("users[0].name")], "Must match first user"),
}),
});Create schemas that are resolved dynamically based on the value being validated, enabling recursive and self-referencing schemas.
/**
* Create a lazy schema that resolves based on the value
* @param builder - Function that returns schema based on value
* @returns LazySchema instance
*/
function lazy<T>(
builder: (value: any, options: ResolveOptions) => Schema<T>
): LazySchema<T>;
interface ResolveOptions {
/** Current validation context */
context?: any;
/** Parent object */
parent?: any;
/** Field path */
path?: string;
}
/**
* Lazy schema that resolves to different schemas based on value
*/
class LazySchema<T = any> {
/**
* Clone the lazy schema
* @param spec - Modifications to apply
* @returns New LazySchema instance
*/
clone(spec?: Partial<LazySchemaSpec>): LazySchema<T>;
/**
* Make the lazy schema optional
* @returns New optional LazySchema
*/
optional(): LazySchema<T | undefined>;
/**
* Resolve to concrete schema based on value
* @param options - Resolution context options
* @returns Resolved concrete schema
*/
resolve(options: ResolveOptions): Schema<T>;
/**
* Cast value using resolved schema
* @param value - Value to cast
* @param options - Cast options
* @returns Cast value
*/
cast(value: any, options?: CastOptions): T;
/**
* Validate value using resolved schema
* @param value - Value to validate
* @param options - Validation options
* @returns Promise resolving to validated value
*/
validate(value: any, options?: ValidateOptions): Promise<T>;
/**
* Synchronously validate value using resolved schema
* @param value - Value to validate
* @param options - Validation options
* @returns Validated value
*/
validateSync(value: any, options?: ValidateOptions): T;
/**
* Validate at specific path using resolved schema
* @param path - Field path
* @param value - Root value
* @param options - Validation options
* @returns Promise resolving to field value
*/
validateAt(path: string, value: any, options?: ValidateOptions): Promise<any>;
/**
* Synchronously validate at path using resolved schema
* @param path - Field path
* @param value - Root value
* @param options - Validation options
* @returns Field value
*/
validateSyncAt(path: string, value: any, options?: ValidateOptions): any;
/**
* Check validity using resolved schema
* @param value - Value to check
* @param options - Validation options
* @returns Promise resolving to validity status
*/
isValid(value: any, options?: ValidateOptions): Promise<boolean>;
/**
* Synchronously check validity using resolved schema
* @param value - Value to check
* @param options - Validation options
* @returns Validity status
*/
isValidSync(value: any, options?: ValidateOptions): boolean;
/**
* Describe the lazy schema or resolved schema
* @param options - Description options
* @returns Schema description
*/
describe(options?: DescribeOptions): SchemaDescription;
/**
* Set or get lazy schema metadata
* @param metadata - Metadata to set (optional)
* @returns Metadata or new schema with metadata
*/
meta(): Record<string, any> | undefined;
meta(metadata: Record<string, any>): LazySchema<T>;
}Usage Examples:
import { object, string, array, lazy } from "yup";
// Recursive schema for nested comments
const commentSchema = lazy(() =>
object({
id: string().required(),
message: string().required(),
author: string().required(),
replies: array(commentSchema), // Self-reference
})
);
// Polymorphic schema based on type field
const shapeSchema = lazy((value) => {
switch (value?.type) {
case "circle":
return object({
type: string().equals(["circle"]),
radius: number().positive().required(),
});
case "rectangle":
return object({
type: string().equals(["rectangle"]),
width: number().positive().required(),
height: number().positive().required(),
});
default:
return object({
type: string().required(),
});
}
});
// Dynamic schema based on user role
const userDataSchema = lazy((value, options) => {
const isAdmin = options.context?.userRole === "admin";
const baseSchema = object({
name: string().required(),
email: string().email().required(),
});
if (isAdmin) {
return baseSchema.shape({
adminNotes: string(),
permissions: array(string()),
});
}
return baseSchema;
});
// Usage with context
await userDataSchema.validate(
{ name: "John", email: "john@example.com" },
{ context: { userRole: "admin" } }
);Format values for display in error messages and debugging.
/**
* Format a value for display in error messages
* @param value - Value to format
* @returns Human-readable string representation
*/
function printValue(value: any): string;Usage Examples:
import { printValue } from "yup";
console.log(printValue("hello")); // '"hello"'
console.log(printValue(42)); // "42"
console.log(printValue(null)); // "null"
console.log(printValue(undefined)); // "undefined"
console.log(printValue([1, 2, 3])); // "[1, 2, 3]"
console.log(printValue({ name: "John" })); // '{"name": "John"}'
console.log(printValue(new Date("2023-01-01"))); // "2023-01-01T00:00:00.000Z"
// Used in custom error messages
const customSchema = string().test(
"custom-test",
function(value) {
return `Expected string, got ${printValue(value)}`;
},
(value) => typeof value === "string"
);Customize error messages for different languages and locales.
/**
* Set custom error messages for validation failures
* @param localeObject - Object containing custom error messages
*/
function setLocale(localeObject: LocaleObject): void;
/**
* Default English error message locale
*/
const defaultLocale: LocaleObject;
interface LocaleObject {
mixed?: MixedLocale;
string?: StringLocale;
number?: NumberLocale;
date?: DateLocale;
boolean?: BooleanLocale;
object?: ObjectLocale;
array?: ArrayLocale;
}
interface MixedLocale {
default?: string;
required?: string;
oneOf?: string;
notOneOf?: string;
defined?: string;
notType?: string;
}
interface StringLocale extends MixedLocale {
length?: string;
min?: string;
max?: string;
matches?: string;
email?: string;
url?: string;
uuid?: string;
trim?: string;
lowercase?: string;
uppercase?: string;
}
interface NumberLocale extends MixedLocale {
min?: string;
max?: string;
lessThan?: string;
moreThan?: string;
positive?: string;
negative?: string;
integer?: string;
}
interface DateLocale extends MixedLocale {
min?: string;
max?: string;
}
interface BooleanLocale extends MixedLocale {
isValue?: string;
}
interface ObjectLocale extends MixedLocale {
noUnknown?: string;
}
interface ArrayLocale extends MixedLocale {
min?: string;
max?: string;
length?: string;
}Usage Examples:
import { setLocale, string, number } from "yup";
// Set Spanish error messages
setLocale({
mixed: {
required: "Este campo es obligatorio",
notType: "Debe ser de tipo ${type}",
},
string: {
min: "Debe tener al menos ${min} caracteres",
max: "Debe tener como máximo ${max} caracteres",
email: "Debe ser un email válido",
},
number: {
min: "Debe ser mayor o igual a ${min}",
max: "Debe ser menor o igual a ${max}",
positive: "Debe ser un número positivo",
},
});
// Schemas will now use Spanish messages
const userSchema = object({
nombre: string().min(2).required(),
edad: number().positive().required(),
email: string().email().required(),
});
// Custom locale for specific domain
setLocale({
string: {
min: "Password must be at least ${min} characters long",
matches: "Password must contain uppercase, lowercase, and numbers",
},
});
// Reset to default locale
setLocale(defaultLocale);Extend schema types with custom validation methods and functionality.
/**
* Add custom methods to schema types
* @param schemaType - Schema constructor to extend
* @param methodName - Name of the method to add
* @param method - Implementation function
*/
function addMethod<T extends Schema>(
schemaType: new (...args: any[]) => T,
methodName: string,
method: (this: T, ...args: any[]) => T
): void;Usage Examples:
import { addMethod, string, StringSchema } from "yup";
// Add custom method to StringSchema
addMethod(StringSchema, "strongPassword", function() {
return this.test(
"strong-password",
"Password must contain uppercase, lowercase, number, and special character",
(value) => {
if (!value) return false;
return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/.test(value);
}
);
});
// Extend TypeScript interface for type safety
declare module "yup" {
interface StringSchema {
strongPassword(): StringSchema;
}
}
// Use custom method
const passwordSchema = string().min(8).strongPassword().required();
// Add method with parameters
addMethod(StringSchema, "containsWord", function(word: string) {
return this.test(
"contains-word",
`Must contain the word "${word}"`,
(value) => value?.includes(word) ?? false
);
});
declare module "yup" {
interface StringSchema {
containsWord(word: string): StringSchema;
}
}
const titleSchema = string().containsWord("yup").required();
// Add method to NumberSchema
addMethod(NumberSchema, "currency", function() {
return this.test(
"currency",
"Must be a valid currency amount",
(value) => {
if (value == null) return true;
return Number.isFinite(value) && Math.round(value * 100) === value * 100;
}
).transform((value) => {
return value != null ? Math.round(value * 100) / 100 : value;
});
});
declare module "yup" {
interface NumberSchema {
currency(): NumberSchema;
}
}
const priceSchema = number().positive().currency().required();Additional utility functions and configurations for specialized use cases.
/**
* Create ValidationError instances programmatically
* @param errors - Error messages or ValidationError instances
* @param value - Value that failed validation
* @param field - Field path where error occurred
* @param type - Type of validation error
* @returns ValidationError instance
*/
function createError(
errors: string | string[] | ValidationError | ValidationError[],
value?: any,
field?: string,
type?: string
): ValidationError;
/**
* Global configuration options for yup behavior
*/
interface YupConfig {
/** Default strict mode setting */
strict?: boolean;
/** Default abort early setting */
abortEarly?: boolean;
/** Default strip unknown setting */
stripUnknown?: boolean;
}
/**
* Configure global yup defaults
* @param config - Global configuration options
*/
function configure(config: YupConfig): void;Usage Examples:
import { createError, configure, ValidationError } from "yup";
// Create custom validation errors
const customError = createError(
"Custom validation failed",
"invalid-value",
"field.path",
"custom"
);
// Multiple errors
const multipleErrors = createError([
"First error message",
"Second error message"
], null, "field");
// Global configuration
configure({
strict: false,
abortEarly: false,
stripUnknown: true,
});
// Custom error aggregation utility
function aggregateErrors(errors: ValidationError[]): Record<string, string[]> {
const result: Record<string, string[]> = {};
errors.forEach(error => {
error.inner.forEach(innerError => {
if (innerError.path) {
if (!result[innerError.path]) {
result[innerError.path] = [];
}
result[innerError.path].push(innerError.message);
}
});
});
return result;
}Install with Tessl CLI
npx tessl i tessl/npm-yup