0
# Validation & Testing
1
2
Comprehensive validation system with async/sync validation, custom tests, conditional validation, and detailed error handling for robust data validation scenarios.
3
4
## Capabilities
5
6
### Core Validation Methods
7
8
Primary methods for validating data against schemas, available on all schema types.
9
10
```typescript { .api }
11
/**
12
* Asynchronously validate a value against the schema
13
* @param value - Value to validate
14
* @param options - Validation configuration options
15
* @returns Promise resolving to validated and transformed value
16
* @throws ValidationError if validation fails
17
*/
18
validate(value: any, options?: ValidateOptions): Promise<T>;
19
20
/**
21
* Synchronously validate a value against the schema
22
* @param value - Value to validate
23
* @param options - Validation configuration options
24
* @returns Validated and transformed value
25
* @throws ValidationError if validation fails
26
*/
27
validateSync(value: any, options?: ValidateOptions): T;
28
29
/**
30
* Validate a nested field at a specific path within an object
31
* @param path - Dot-notation path to the field
32
* @param value - Root object containing the field
33
* @param options - Validation configuration options
34
* @returns Promise resolving to validated field value
35
*/
36
validateAt(path: string, value: any, options?: ValidateOptions): Promise<any>;
37
38
/**
39
* Synchronously validate a nested field at a specific path
40
* @param path - Dot-notation path to the field
41
* @param value - Root object containing the field
42
* @param options - Validation configuration options
43
* @returns Validated field value
44
*/
45
validateSyncAt(path: string, value: any, options?: ValidateOptions): any;
46
47
/**
48
* Check if a value is valid without throwing errors
49
* @param value - Value to check
50
* @param options - Validation configuration options
51
* @returns Promise resolving to boolean validity status
52
*/
53
isValid(value: any, options?: ValidateOptions): Promise<boolean>;
54
55
/**
56
* Synchronously check if a value is valid without throwing errors
57
* @param value - Value to check
58
* @param options - Validation configuration options
59
* @returns Boolean validity status
60
*/
61
isValidSync(value: any, options?: ValidateOptions): boolean;
62
63
/**
64
* Check if a value matches the schema's expected type
65
* @param value - Value to type-check
66
* @returns Type predicate indicating if value matches schema type
67
*/
68
isType(value: any): value is T;
69
70
interface ValidateOptions<TContext = {}> {
71
/** Enable strict validation (no type coercion) */
72
strict?: boolean;
73
/** Stop validation on first error (default: true) */
74
abortEarly?: boolean;
75
/** Remove unknown fields from objects */
76
stripUnknown?: boolean;
77
/** Validate nested schemas recursively */
78
recursive?: boolean;
79
/** Additional context data available during validation */
80
context?: TContext;
81
}
82
```
83
84
**Usage Examples:**
85
86
```typescript
87
import { object, string, number } from "yup";
88
89
const userSchema = object({
90
name: string().required(),
91
age: number().positive().integer(),
92
});
93
94
// Async validation
95
try {
96
const user = await userSchema.validate({
97
name: "John",
98
age: 25
99
});
100
console.log(user); // { name: "John", age: 25 }
101
} catch (error) {
102
console.error(error.errors); // Array of error messages
103
}
104
105
// Sync validation
106
try {
107
const user = userSchema.validateSync({ name: "John", age: 25 });
108
} catch (error) {
109
console.error(error.message);
110
}
111
112
// Check validity without throwing
113
const isValid = await userSchema.isValid({ name: "John", age: -5 }); // false
114
115
// Validate nested field
116
await userSchema.validateAt("name", { name: "John", age: 25 }); // "John"
117
```
118
119
### Value Casting
120
121
Transform and coerce values to match schema types without full validation.
122
123
```typescript { .api }
124
/**
125
* Cast/transform a value to match the schema type
126
* @param value - Value to cast
127
* @param options - Casting configuration options
128
* @returns Transformed value matching schema type
129
*/
130
cast(value: any, options?: CastOptions): T;
131
132
interface CastOptions<TContext = {}> {
133
/** Enable strict casting (minimal transformations) */
134
strict?: boolean;
135
/** Remove unknown fields from objects */
136
stripUnknown?: boolean;
137
/** Additional context data available during casting */
138
context?: TContext;
139
}
140
```
141
142
**Usage Examples:**
143
144
```typescript
145
import { string, number, date } from "yup";
146
147
// String casting
148
const stringSchema = string();
149
stringSchema.cast(123); // "123"
150
stringSchema.cast(true); // "true"
151
152
// Number casting
153
const numberSchema = number();
154
numberSchema.cast("42"); // 42
155
numberSchema.cast("3.14"); // 3.14
156
157
// Date casting
158
const dateSchema = date();
159
dateSchema.cast("2023-01-01"); // Date object
160
dateSchema.cast(1640995200000); // Date from timestamp
161
```
162
163
### Custom Validation Tests
164
165
Add custom validation logic with detailed error reporting and context access.
166
167
```typescript { .api }
168
/**
169
* Add a custom validation test to the schema
170
* @param name - Test name for error reporting
171
* @param message - Error message when test fails
172
* @param testFn - Function that returns true if valid
173
* @returns New schema with custom test
174
*/
175
test(name: string, message: string, testFn: TestFunction): Schema;
176
177
/**
178
* Add a custom validation test with full configuration
179
* @param options - Complete test configuration
180
* @returns New schema with custom test
181
*/
182
test(options: TestConfig): Schema;
183
184
type TestFunction<T = any, C = any> = (
185
value: T,
186
context: TestContext<T, C>
187
) => boolean | Promise<boolean>;
188
189
interface TestConfig<T = any, C = any> {
190
/** Unique name for the test */
191
name: string;
192
/** Error message when test fails (string or function) */
193
message: string | ((params: TestMessageParams) => string);
194
/** Test function that validates the value */
195
test: TestFunction<T, C>;
196
/** Skip test if value is empty/null/undefined */
197
skipAbsent?: boolean;
198
/** Test is exclusive - removes other tests with same name */
199
exclusive?: boolean;
200
/** Parameters to pass to error message function */
201
params?: Record<string, any>;
202
}
203
204
interface TestContext<T = any, C = any> {
205
/** Path to current field being validated */
206
path: string;
207
/** Field name being validated */
208
key?: string;
209
/** Parent object containing the field */
210
parent: any;
211
/** Root value being validated */
212
from: Array<{ schema: Schema; value: any }>;
213
/** Additional context data */
214
options: ValidateOptions<C>;
215
/** Create ValidationError for this test */
216
createError(params?: CreateErrorOptions): ValidationError;
217
/** Resolve references and lazy schemas */
218
resolve(value: any): any;
219
}
220
221
interface CreateErrorOptions {
222
/** Override default error message */
223
message?: string;
224
/** Override field path */
225
path?: string;
226
/** Override field value */
227
value?: any;
228
/** Additional parameters for message interpolation */
229
params?: Record<string, any>;
230
}
231
```
232
233
**Usage Examples:**
234
235
```typescript
236
import { string, number, object } from "yup";
237
238
// Simple custom test
239
const evenNumberSchema = number().test(
240
"is-even",
241
"Number must be even",
242
(value) => value % 2 === 0
243
);
244
245
// Async custom test with API call
246
const uniqueEmailSchema = string().test(
247
"unique-email",
248
"Email already exists",
249
async (email) => {
250
const exists = await checkEmailExists(email);
251
return !exists;
252
}
253
);
254
255
// Custom test with context access
256
const passwordSchema = object({
257
password: string().min(8),
258
confirmPassword: string().test(
259
"passwords-match",
260
"Passwords must match",
261
function(value) {
262
return value === this.parent.password;
263
}
264
),
265
});
266
267
// Custom test with dynamic message
268
const minLengthSchema = string().test({
269
name: "min-length",
270
message: ({ min }) => `Must be at least ${min} characters`,
271
params: { min: 5 },
272
test: (value) => value && value.length >= 5,
273
});
274
```
275
276
### Conditional Validation
277
278
Create dynamic validation rules based on other field values or conditions.
279
280
```typescript { .api }
281
/**
282
* Apply conditional validation based on other fields
283
* @param keys - Field name(s) to check for condition
284
* @param builder - Function or config that returns schema based on condition
285
* @returns New schema with conditional validation
286
*/
287
when<U extends Schema>(
288
keys: string | string[],
289
builder: WhenBuilder<U> | WhenBuilderOptions<U>
290
): Schema;
291
292
type WhenBuilder<U extends Schema> = (
293
value: any,
294
schema: Schema
295
) => U;
296
297
interface WhenBuilderOptions<U extends Schema> {
298
/** Value(s) to match against */
299
is?: any | any[];
300
/** Schema to use when condition matches */
301
then?: U | ((schema: Schema) => U);
302
/** Schema to use when condition doesn't match */
303
otherwise?: U | ((schema: Schema) => U);
304
}
305
```
306
307
**Usage Examples:**
308
309
```typescript
310
import { object, string, boolean, number } from "yup";
311
312
// Simple conditional validation
313
const schema = object({
314
isBusiness: boolean(),
315
companyName: string().when("isBusiness", {
316
is: true,
317
then: (schema) => schema.required("Company name is required"),
318
otherwise: (schema) => schema.strip(),
319
}),
320
});
321
322
// Multiple conditions
323
const subscriptionSchema = object({
324
type: string().oneOf(["free", "premium", "enterprise"]),
325
seats: number().when("type", {
326
is: "enterprise",
327
then: (schema) => schema.min(10).required(),
328
otherwise: (schema) => schema.max(5),
329
}),
330
customDomain: string().when("type", (type, schema) => {
331
return type === "premium" || type === "enterprise"
332
? schema.required()
333
: schema.strip();
334
}),
335
});
336
337
// Multiple field dependencies
338
const addressSchema = object({
339
country: string().required(),
340
state: string().when("country", {
341
is: "US",
342
then: (schema) => schema.required(),
343
}),
344
zipCode: string().when(["country", "state"], {
345
is: (country, state) => country === "US" && state,
346
then: (schema) => schema.matches(/^\d{5}$/, "Invalid ZIP code"),
347
}),
348
});
349
```
350
351
### Error Handling
352
353
Comprehensive error information with detailed validation failure reporting.
354
355
```typescript { .api }
356
/**
357
* Error thrown when validation fails
358
*/
359
class ValidationError extends Error {
360
/** Always "ValidationError" */
361
name: "ValidationError";
362
/** Primary error message */
363
message: string;
364
/** Value that failed validation */
365
value: any;
366
/** Path where validation failed */
367
path?: string;
368
/** Type of validation that failed */
369
type?: string;
370
/** Parameters used in error message */
371
params?: Record<string, any>;
372
/** Array of all error messages */
373
errors: string[];
374
/** Array of nested ValidationError objects */
375
inner: ValidationError[];
376
377
/**
378
* Format error message with parameters
379
* @param message - Message template
380
* @param params - Parameters for interpolation
381
* @returns Formatted message
382
*/
383
static formatError(
384
message: string | ((params: any) => string),
385
params: Record<string, any>
386
): string;
387
388
/**
389
* Check if an error is a ValidationError
390
* @param err - Error to check
391
* @returns Type predicate for ValidationError
392
*/
393
static isError(err: any): err is ValidationError;
394
}
395
```
396
397
**Usage Examples:**
398
399
```typescript
400
import { object, string, ValidationError } from "yup";
401
402
const schema = object({
403
name: string().required("Name is required"),
404
email: string().email("Invalid email format").required(),
405
});
406
407
try {
408
await schema.validate({ name: "", email: "not-email" });
409
} catch (error) {
410
if (ValidationError.isError(error)) {
411
console.log("Validation failed!");
412
console.log("Primary message:", error.message);
413
console.log("All errors:", error.errors);
414
console.log("Failed at path:", error.path);
415
console.log("Failed value:", error.value);
416
417
// Access nested errors
418
error.inner.forEach((nestedError) => {
419
console.log(`${nestedError.path}: ${nestedError.message}`);
420
});
421
}
422
}
423
424
// Custom error handling
425
const handleValidationError = (error: ValidationError) => {
426
const fieldErrors: Record<string, string> = {};
427
428
error.inner.forEach((err) => {
429
if (err.path) {
430
fieldErrors[err.path] = err.message;
431
}
432
});
433
434
return fieldErrors;
435
};
436
```
437
438
### Transformation Functions
439
440
Apply custom transformations to values during validation and casting.
441
442
```typescript { .api }
443
/**
444
* Add a transformation function to the schema
445
* @param transformFn - Function to transform the value
446
* @returns New schema with transformation applied
447
*/
448
transform<U>(transformFn: TransformFunction<T, U>): Schema<U>;
449
450
type TransformFunction<T, U> = (
451
currentValue: T,
452
originalValue: any,
453
context: TransformContext
454
) => U;
455
456
interface TransformContext {
457
/** Path to current field */
458
path: string;
459
/** Parent object */
460
parent: any;
461
/** Root value being transformed */
462
root: any;
463
/** Additional context data */
464
options: CastOptions;
465
}
466
```
467
468
**Usage Examples:**
469
470
```typescript
471
import { string, number, array } from "yup";
472
473
// String transformation
474
const slugSchema = string()
475
.transform((value) => value?.toLowerCase().replace(/\s+/g, "-"))
476
.matches(/^[a-z0-9-]+$/, "Invalid slug format");
477
478
// Number transformation
479
const currencySchema = string()
480
.transform((value) => {
481
// Remove currency symbols and convert to number
482
const cleaned = value?.replace(/[$,]/g, "");
483
return parseFloat(cleaned) || 0;
484
})
485
.transform((value) => Math.round(value * 100) / 100); // Round to 2 decimals
486
487
// Array transformation
488
const csvToArraySchema = string()
489
.transform((value) => value?.split(",").map(s => s.trim()))
490
.transform((arr) => arr?.filter(Boolean)); // Remove empty strings
491
492
// Context-aware transformation
493
const fullNameSchema = object({
494
firstName: string().required(),
495
lastName: string().required(),
496
fullName: string().transform(function(value, originalValue, context) {
497
// Auto-generate full name if not provided
498
if (!value && context.parent) {
499
return `${context.parent.firstName} ${context.parent.lastName}`;
500
}
501
return value;
502
}),
503
});
504
```
505
506
### Metadata and Labeling
507
508
Add metadata and labels to schemas for enhanced error reporting and documentation.
509
510
```typescript { .api }
511
/**
512
* Set or get metadata for the schema
513
* @param metadata - Metadata object to set (optional)
514
* @returns Schema metadata or new schema with metadata
515
*/
516
meta(): Record<string, any> | undefined;
517
meta(metadata: Record<string, any>): Schema;
518
519
/**
520
* Set a human-readable label for better error messages
521
* @param label - Human-readable field label
522
* @returns New schema with label
523
*/
524
label(label: string): Schema;
525
526
/**
527
* Set custom error message for type validation failures
528
* @param message - Custom type error message
529
* @returns New schema with custom type error
530
*/
531
typeError(message: string): Schema;
532
533
/**
534
* Get a description of the schema structure and rules
535
* @param options - Description options
536
* @returns Schema description object
537
*/
538
describe(options?: DescribeOptions): SchemaDescription;
539
540
interface DescribeOptions {
541
/** Include field values in description */
542
value?: any;
543
/** Additional context */
544
context?: any;
545
}
546
547
interface SchemaDescription {
548
/** Schema type */
549
type: string;
550
/** Human-readable label */
551
label?: string;
552
/** Schema metadata */
553
meta?: Record<string, any>;
554
/** Whether field is required */
555
optional: boolean;
556
/** Whether field is nullable */
557
nullable: boolean;
558
/** Default value */
559
default?: any;
560
/** Applied tests and validations */
561
tests: Array<{ name: string; params?: any }>;
562
/** Inner type for arrays/objects */
563
innerType?: SchemaDescription;
564
/** Object fields */
565
fields?: Record<string, SchemaDescription>;
566
}
567
```
568
569
**Usage Examples:**
570
571
```typescript
572
import { string, object } from "yup";
573
574
// Schema with labels and metadata
575
const userSchema = object({
576
email: string()
577
.email()
578
.required()
579
.label("Email Address")
580
.meta({
581
category: "contact",
582
sensitive: true,
583
helpText: "We'll use this to send you updates"
584
}),
585
586
username: string()
587
.matches(/^[a-zA-Z0-9_]+$/)
588
.required()
589
.label("Username")
590
.typeError("Username must be a string")
591
.meta({ category: "identity" }),
592
});
593
594
// Access metadata
595
const emailMeta = userSchema.fields.email.meta();
596
console.log(emailMeta.helpText); // "We'll use this to send you updates"
597
598
// Schema description
599
const description = userSchema.describe();
600
console.log(description.fields.email.label); // "Email Address"
601
console.log(description.fields.email.tests); // Array of validation tests
602
```