Yup
Dead simple Object schema validation
README
Yup
You are viewing docs for the v1.0.0 pre-release of yup, pre-v1 docs are available: here
Getting Started
- ```ts
- import { object, string, number, date, InferType } from 'yup';
- let userSchema = object({
- name: string().required(),
- age: number().required().positive().integer(),
- email: string().email(),
- website: string().url().nullable(),
- createdOn: date().default(() => new Date()),
- });
- // parse and assert validity
- const user = await userSchema.validate(await fetchUser());
- type User = InferType<typeof userSchema>;
- /* {
- name: string;
- age: number;
- email?: string | undefined
- website?: string | null | undefined
- createdOn: Date
- }*/
- ```
- ```ts
- // Attempts to coerce values to the correct type
- const parsedUser = userSchema.cast({
- name: 'jimmy',
- age: '24',
- createdOn: '2014-09-23T19:25:25Z',
- });
- // ✅ { name: 'jimmy', age: 24, createdOn: Date }
- ```
- ```ts
- // ❌ ValidationError "age is not a number"
- const parsedUser = await userSchema.validate(
- {
- name: 'jimmy',
- age: '24',
- },
- { strict: true },
- );
- ```
Table of Contents
- ``` js
- import {
- BooleanSchema,
- DateSchema,
- MixedSchema,
- NumberSchema,
- ArraySchema,
- ObjectSchema,
- StringSchema,
- } from 'yup';
- ``` -->
Schema basics
Parsing: Transforms
- ```ts
- const num = number().cast('1'); // 1
- const obj = object({
- firstName: string().lowercase().trim(),
- })
- .camelCase()
- .cast('{"first_name": "jAnE "}'); // { firstName: 'jane' }
- ```
- ```ts
- const reversedString = string()
- .transform((currentValue) => currentValue.split('').reverse().join(''))
- .cast('dlrow olleh'); // "hello world"
- ```
Watch out! values are not guaranteed to be valid types in transform functions. Previous transforms
may have failed. For example a number transform may be receive the input value, NaN, or a number.
Validation: Tests
- ```ts
- string()
- .min(3, 'must be at least 3 characters long')
- .email('must be a valid email')
- .validate('no'); // ValidationError
- ```
- ```ts
- const jamesSchema = string().test(
- 'is-james',
- (d) => `${d.path} is not James`,
- (value) => value == null || value === 'James',
- );
- jamesSchema.validateSync('James'); // "James"
- jamesSchema.validateSync('Jane'); // ValidationError "this is not James"
- ```
Heads up: unlike transforms, value in a custom test is guaranteed to be the correct type
(in this case an optional string). It still may be undefined or null depending on your schema
in those cases, you may want to return true for absent values unless your transform makes presence
related assertions
Customizing errors
- ```ts
- const order = object({
- no: number().required().
- sku: string().test({
- name: 'is-sku',
- skipAbsent: true,
- test(value, ctx) {
- if (!value.startsWith('s-')) {
- return ctx.createError({ message: 'SKU missing correct prefix' })
- }
- if (!value.endsWith('-42a')) {
- return ctx.createError({ message: 'SKU missing correct suffix' })
- }
- if (value.length < 10) {
- return ctx.createError({ message: 'SKU is not the right length' })
- }
- return true
- }
- })
- })
- order.validate({ no: 1234, sku: 's-1a45-14a' })
- ```
Composition and Reuse
- ```ts
- const optionalString = string().optional();
- const definedString = optionalString.defined();
- const value = undefined;
- optionalString.isValid(value); // true
- definedString.isValid(value); // false
- ```
TypeScript integration
- ```ts
- import * as yup from 'yup';
- const personSchema = yup.object({
- firstName: yup.string().defined(),
- nickName: yup.string().default('').nullable(),
- sex: yup
- .mixed()
- .oneOf(['male', 'female', 'other'] as const)
- .defined(),
- email: yup.string().nullable().email(),
- birthDate: yup.date().nullable().min(new Date(1900, 0, 1)),
- });
- interface Person extends yup.InferType<typeof personSchema> {
- // using interface instead of type generally gives nicer editor feedback
- }
- ```
Schema defaults
- ```ts
- import { string } from 'yup';
- const value: string = string().default('hi').validate(undefined);
- // vs
- const value: string | undefined = string().validate(undefined);
- ```
Ensuring a schema matches an existing type
- ```ts
- import { object, number, string, ObjectSchema } from 'yup';
- interface Person {
- name: string;
- age?: number;
- sex: 'male' | 'female' | 'other' | null;
- }
- // will raise a compile-time type error if the schema does not produce a valid Person
- const schema: ObjectSchema<Person> = object({
- name: string().defined(),
- age: number().optional(),
- sex: string<'male' | 'female' | 'other'>().nullable().defined();
- });
- // ❌ errors:
- // "Type 'number | undefined' is not assignable to type 'string'."
- const badSchema: ObjectSchema<Person> = object({
- name: number(),
- });
- ```
Extending built-in schema with new methods
Watch out! merging only works if the type definition is _exactly_ the same, including
generics. Consult the yup source code for each type to ensure you are defining it correctly
- ```ts
- // globals.d.ts
- declare module 'yup' {
- interface StringSchema<TType, TContext, TDefault, TFlags> {
- append(appendStr: string): this;
- }
- }
- // app.ts
- import { addMethod, string } from 'yup';
- addMethod(string, 'append', function append(appendStr: string) {
- return this.transform((value) => `${value}${appendStr}`);
- });
- string().append('~~~~').cast('hi'); // 'hi~~~~'
- ```
TypeScript configuration
During development of this feature, we discovered a large number of inherently
unsafe class hierarchies, including some in the DOM. Because of this,
the setting only applies to functions written in function syntax, not to those in method syntax:
Error message customization
- ``` js
- import { setLocale } from 'yup';
- setLocale({
- mixed: {
- default: 'Não é válido',
- },
- number: {
- min: 'Deve ser maior que ${min}',
- },
- });
- // now use Yup schemas AFTER you defined your custom dictionary
- let schema = yup.object().shape({
- name: yup.string(),
- age: yup.number().min(18),
- });
- try {
- await schema.validate({ name: 'jimmy', age: 11 });
- } catch (err) {
- err.name; // => 'ValidationError'
- err.errors; // => ['Deve ser maior que 18']
- }
- ```
localization and i18n
- ``` js
- import { setLocale } from 'yup';
- setLocale({
- // use constant translation keys for messages without values
- mixed: {
- default: 'field_invalid',
- },
- // use functions to generate an error object that includes the value from the schema
- number: {
- min: ({ min }) => ({ key: 'field_too_short', values: { min } }),
- max: ({ max }) => ({ key: 'field_too_big', values: { max } }),
- },
- });
- // ...
- let schema = yup.object().shape({
- name: yup.string(),
- age: yup.number().min(18),
- });
- try {
- await schema.validate({ name: 'jimmy', age: 11 });
- } catch (err) {
- messages = err.errors.map((err) => i18next.t(err.key));
- }
- ```
API
yup
- ```ts
- // core schema
- import {
- mixed,
- string,
- number,
- boolean,
- bool,
- date,
- object,
- array,
- ref,
- lazy,
- } from 'yup';
- // Classes
- import {
- Schema,
- MixedSchema,
- StringSchema,
- NumberSchema,
- BooleanSchema,
- DateSchema,
- ArraySchema,
- ObjectSchema,
- } from 'yup';
- // Types
- import type { InferType, ISchema, AnySchema, AnyObjectSchema } from 'yup';
- ```
reach(schema: Schema, path: string, value?: object, context?: object): Schema
- ``` js
- import { reach } from 'yup';
- let schema = object({
- nested: object({
- arr: array(object({ num: number().max(4) })),
- }),
- });
- reach(schema, 'nested.arr.num');
- reach(schema, 'nested.arr[].num');
- reach(schema, 'nested.arr[1].num');
- reach(schema, 'nested["arr"][1].num');
- ```
addMethod(schemaType: Schema, name: string, method: ()=> Schema): void
- ```ts
- import { addMethod, date } from 'yup';
- addMethod(date, 'format', function format(formats, parseStrict) {
- return this.transform((value, originalValue, ctx) => {
- if (ctx.isType(value)) return value;
- value = Moment(originalValue, formats, parseStrict);
- return value.isValid() ? value.toDate() : new Date('');
- });
- });
- ```
- ```ts
- import { addMethod, Schema } from 'yup';
- addMethod(Schema, 'myMethod', ...)
- ```
ref(path: string, options: { contextPrefix: string }): Ref
- ``` js
- import { ref, object, string } from 'yup';
- let schema = object({
- baz: ref('foo.bar'),
- foo: object({
- bar: string(),
- }),
- x: ref('$x'),
- });
- schema.cast({ foo: { bar: 'boom' } }, { context: { x: 5 } });
- // => { baz: 'boom', x: 5, foo: { bar: 'boom' } }
- ```
lazy((value: any) => Schema): Lazy
- ``` js
- let node = object({
- id: number(),
- child: yup.lazy(() => node.default(undefined)),
- });
- let renderable = yup.lazy((value) => {
- switch (typeof value) {
- case 'number':
- return number();
- case 'string':
- return string();
- default:
- return mixed();
- }
- });
- let renderables = array().of(renderable);
- ```
Schema
Note: unless you are creating a custom schema type, Schema should never be used directly. For unknown/any types use [mixed()](#mixed)
Schema.clone(): Schema
Schema.label(label: string): Schema
Schema.meta(metadata: object): Schema
Schema.describe(options?: ResolveOptions): SchemaDescription
- ```ts
- const schema = object({
- name: string().required(),
- });
- const description = schema.describe();
- ```
- ```ts
- import { ref, object, string, boolean } from 'yup';
- let schema = object({
- isBig: boolean(),
- count: number().when('isBig', {
- is: true,
- then: (schema) => schema.min(5),
- otherwise: (schema) => schema.min(0),
- }),
- });
- schema.describe({ value: { isBig: true } });
- ```
- ```ts
- interface SchemaDescription {
- type: string;
- label?: string;
- meta: object | undefined;
- oneOf: unknown[];
- notOneOf: unknown[];
- nullable: boolean;
- optional: boolean;
- tests: Array<{ name?: string; params: ExtraParams | undefined }>;
- // Present on object schema descriptions
- fields: Record<string, SchemaFieldDescription>;
- // Present on array schema descriptions
- innerType?: SchemaFieldDescription;
- }
- type SchemaFieldDescription =
- | SchemaDescription
- | SchemaRefDescription
- | SchemaLazyDescription;
- interface SchemaRefDescription {
- type: 'ref';
- key: string;
- }
- interface SchemaLazyDescription {
- type: string;
- label?: string;
- meta: object | undefined;
- }
- ```
Schema.concat(schema: Schema): Schema
- ```ts
- mixed<string>().defined().concat(mixed<number>().nullable());
- // produces the equivalent to:
- mixed<number>().defined().nullable();
- ```
- ``` js
- value = await schema.validate({ name: 'jimmy', age: 24 });
- ```
- ``` js
- interface Options {
- // when true, parsing is skipped an the input is validated "as-is"
- strict: boolean = false;
- // Throw on the first error or collect and return all
- abortEarly: boolean = true;
- // Remove unspecified keys from objects
- stripUnknown: boolean = false;
- // when `false` validations will be performed shallowly
- recursive: boolean = true;
- // External values that can be provided to validations and conditionals
- context?: object;
- }
- ```
- ``` js
- let schema = number().test(
- 'is-42',
- "this isn't the number i want",
- (value) => value != 42,
- );
- schema.validateSync(23); // throws ValidationError
- ```
- ``` js
- let schema = number().test('is-42', "this isn't the number i want", (value) =>
- Promise.resolve(value != 42),
- );
- schema.validateSync(42); // throws Error
- ```
Note! The value here is the _root_ value relative to the starting schema, not the value at the nested path.
- ``` js
- let schema = object({
- foo: array().of(
- object({
- loose: boolean(),
- bar: string().when('loose', {
- is: true,
- otherwise: (schema) => schema.strict(),
- }),
- }),
- ),
- });
- let rootValue = {
- foo: [{ bar: 1 }, { bar: 1, loose: true }],
- };
- await schema.validateAt('foo[0].bar', rootValue); // => ValidationError: must be a string
- await schema.validateAt('foo[1].bar', rootValue); // => '1'
- ```
Schema.isValidSync(value: any, options?: object): boolean
- ``` js
- interface CastOptions<TContext extends {}> {
- // Remove undefined properties from objects
- stripUnknown: boolean = false;
- // Throws a TypeError if casting doesn't produce a valid type
- // note that the TS return type is inaccurate when this is `false`, use with caution
- assert?: boolean = true;
- // External values that used to resolve conditions and references
- context?: TContext;
- }
- ```
Schema.strict(enabled: boolean = false): Schema
Schema.strip(enabled: boolean = true): Schema
- ``` js
- let schema = object({
- useThis: number(),
- notThis: string().strip(),
- });
- schema.cast({ notThis: 'foo', useThis: 4 }); // => { useThis: 4 }
- ```
- ```ts
- let schema = object({
- useThis: number(),
- notThis: string().strip(),
- });
- InferType<typeof schema>; /*
- {
- useThis?: number | undefined
- }
- */
- ```
Schema.withMutation(builder: (current: Schema) => void): void
If a tree falls in the woods, does it make a sound?
If a pure function mutates some local data in order to produce an immutable return value, is that ok?
- ``` js
- object()
- .shape({ key: string() })
- .withMutation((schema) => {
- return arrayOfObjectTests.forEach((test) => {
- schema.test(test);
- });
- });
- ```
Schema.default(value: any): Schema
- ``` js
- yup.string.default('nothing');
- yup.object.default({ number: 5 }); // object will be cloned every time a default is needed
- yup.object.default(() => ({ number: 5 })); // this is cheaper
- yup.date.default(() => new Date()); // also helpful for defaults that change over time
- ```
Schema.getDefault(options?: object): Any
Schema.nullable(): Schema
- ```ts
- const schema = number().nullable();
- schema.cast(null); // null
- InferType<typeof schema>; // number | null
- ```
Schema.nonNullable(): Schema
- ```ts
- const schema = number().nonNullable();
- schema.cast(null); // TypeError
- InferType<typeof schema>; // number
- ```
Schema.defined(): Schema
- ```ts
- const schema = string().defined();
- schema.cast(undefined); // TypeError
- InferType<typeof schema>; // string
- ```
Schema.optional(): Schema
- ```ts
- const schema = string().optional();
- schema.cast(undefined); // undefined
- InferType<typeof schema>; // string | undefined
- ```
Schema.required(message?: string | function): Schema
Watch out! [string().required](#stringrequiredmessage-string--function-schema)) works a little
different and additionally prevents empty string values ('') when required.
Schema.notRequired(): Schema Alias: optional()
Schema.typeError(message: string): Schema
- ``` js
- let schema = yup.mixed().oneOf(['jimmy', 42]);
- await schema.isValid(42); // => true
- await schema.isValid('jimmy'); // => true
- await schema.isValid(new Date()); // => false
- ```
- ``` js
- let schema = yup.mixed().notOneOf(['jimmy', 42]);
- await schema.isValid(42); // => false
- await schema.isValid(new Date()); // => true
- ```
Schema.when(keys: string | string[], builder: object | (values: any[], schema) => Schema): Schema
- ``` js
- let schema = object({
- isBig: boolean(),
- count: number()
- .when('isBig', {
- is: true, // alternatively: (val) => val == true
- then: (schema) => schema.min(5),
- otherwise: (schema) => schema.min(0),
- })
- .when('$other', ([other], schema) =>
- other === 4 ? schema.max(6) : schema,
- ),
- });
- await schema.validate(value, { context: { other: 4 } });
- ```
- ``` js
- let schema = object({
- isSpecial: boolean(),
- isBig: boolean(),
- count: number().when(['isBig', 'isSpecial'], {
- is: true, // alternatively: (isBig, isSpecial) => isBig && isSpecial
- then: (schema) => schema.min(5),
- otherwise: (schema) => schema.min(0),
- }),
- });
- await schema.validate({
- isBig: true,
- isSpecial: true,
- count: 10,
- });
- ```
- ``` js
- let schema = yup.object({
- isBig: yup.boolean(),
- count: yup.number().when('isBig', ([isBig], schema) => {
- return isBig ? schema.min(5) : schema.min(0);
- }),
- });
- await schema.validate({ isBig: false, count: 4 });
- ```
Schema.test(name: string, message: string | function | any, test: function): Schema
- ``` js
- let jimmySchema = string().test(
- 'is-jimmy',
- '${path} is not Jimmy',
- (value, context) => value === 'jimmy',
- );
- // or make it async by returning a promise
- let asyncJimmySchema = string()
- .label('First name')
- .test(
- 'is-jimmy',
- ({ label }) => `${label} is not Jimmy`, // a message can also be a function
- async (value, testContext) =>
- (await fetch('/is-jimmy/' + value)).responseText === 'true',
- );
- await schema.isValid('jimmy'); // => true
- await schema.isValid('john'); // => false
- ```
Schema.test(options: object): Schema
- ``` js
- Options = {
- // unique name identifying the test
- name: string;
- // test function, determines schema validity
- test: (value: any) => boolean;
- // the validation error message
- message: string;
- // values passed to message for interpolation
- params: ?object;
- // mark the test as exclusive, meaning only one test of the same name can be active at once
- exclusive: boolean = false;
- }
- ```
- ``` js
- let max = 64;
- let schema = yup.string().test({
- name: 'max',
- exclusive: true,
- params: { max },
- message: '${path} must be less than ${max} characters',
- test: (value) => value == null || value.length <= max,
- });
- ```
Schema.transform((currentValue: any, originalValue: any) => any): Schema
- ``` js
- let schema = string().transform((value, originalvalue) => {
- return this.isType(value) && value !== null ? value.toUpperCase() : value;
- });
- schema.cast('jimmy'); // => 'JIMMY'
- ```
- ``` js
- module.exports = function (formats = 'MMM dd, yyyy') {
- return date().transform((value, originalValue, context) => {
- // check to see if the previous transform already parsed the date
- if (context.isType(value)) return value;
- // the default coercion failed so let's try it with Moment.js instead
- value = Moment(originalValue, formats);
- // if it's valid return the date object, otherwise return an `InvalidDate`
- return value.isValid() ? value.toDate() : new Date('');
- });
- };
- ```
mixed
- ```ts
- import { mixed, InferType } from 'yup';
- let schema = mixed().nullable();
- schema.validateSync('string'); // 'string';
- schema.validateSync(1); // 1;
- schema.validateSync(new Date()); // Date;
- InferType<typeof schema>; // {} | undefined
- InferType<typeof schema.nullable().defined()>; // {} | null
- ```
- ```ts
- import { mixed, InferType } from 'yup';
- let objectIdSchema = yup
- .mixed((input): input is ObjectId => input instanceof ObjectId)
- .transform((value: any, input, ctx) => {
- if (ctx.typeCheck(value)) return value;
- return new ObjectId(value);
- });
- await objectIdSchema.validate(ObjectId('507f1f77bcf86cd799439011')); // ObjectId("507f1f77bcf86cd799439011")
- await objectIdSchema.validate('507f1f77bcf86cd799439011'); // ObjectId("507f1f77bcf86cd799439011")
- InferType<typeof objectIdSchema>; // ObjectId
- ```
string
- ``` js
- let schema = yup.string();
- await schema.isValid('hello'); // => true
- ```
string.required(message?: string | function): Schema
string.length(limit: number | Ref, message?: string | function): Schema
string.min(limit: number | Ref, message?: string | function): Schema
string.max(limit: number | Ref, message?: string | function): Schema
string.matches(regex: Regex, message?: string | function): Schema
- ``` js
- let schema = string().matches(/(hi|bye)/);
- await schema.isValid('hi'); // => true
- await schema.isValid('nope'); // => false
- ```
string.matches(regex: Regex, options: { message: string, excludeEmptyString: bool }): Schema
- ``` js
- let schema = string().matches(/(hi|bye)/, { excludeEmptyString: true });
- await schema.isValid(''); // => true
- ```
string.email(message?: string | function): Schema
- ```ts
- yup.addMethod(yup.string, 'email', function validateEmail(message) {
- return this.matches(myEmailRegex, {
- message,
- name: 'email',
- excludeEmptyString: true,
- });
- });
- ```
string.url(message?: string | function): Schema
string.uuid(message?: string | function): Schema
string.ensure(): Schema
string.trim(message?: string | function): Schema
string.lowercase(message?: string | function): Schema
string.uppercase(message?: string | function): Schema
number
- ``` js
- let schema = yup.number();
- await schema.isValid(10); // => true
- ```
number.min(limit: number | Ref, message?: string | function): Schema
number.max(limit: number | Ref, message?: string | function): Schema
number.lessThan(max: number | Ref, message?: string | function): Schema
number.moreThan(min: number | Ref, message?: string | function): Schema
number.positive(message?: string | function): Schema
number.negative(message?: string | function): Schema
number.integer(message?: string | function): Schema
number.truncate(): Schema
number.round(type: 'floor' | 'ceil' | 'trunc' | 'round' = 'round'): Schema
boolean
- ``` js
- let schema = yup.boolean();
- await schema.isValid(true); // => true
- ```
date
- ``` js
- let schema = yup.date();
- await schema.isValid(new Date()); // => true
- ```
date.min(limit: Date | string | Ref, message?: string | function): Schema
date.max(limit: Date | string | Ref, message?: string | function): Schema
array
- ``` js
- let schema = yup.array().of(yup.number().min(2));
- await schema.isValid([2, 3]); // => true
- await schema.isValid([1, -24]); // => false
- schema.cast(['2', '3']); // => [2, 3]
- ```
- ``` js
- array().of(yup.number());
- // or
- array(yup.number());
- ```
array.of(type: Schema): this
array.json(): this
array.length(length: number | Ref, message?: string | function): this
array.min(limit: number | Ref, message?: string | function): this
array.max(limit: number | Ref, message?: string | function): this
array.ensure(): this
- ``` js
- array().ensure().cast(null); // => []
- array().ensure().cast(1); // => [1]
- array().ensure().cast([1]); // => [1]
- ```
array.compact(rejector: (value) => boolean): Schema
- ``` js
- array().compact().cast(['', 1, 0, 4, false, null]); // => [1, 4]
- array()
- .compact(function (v) {
- return v == null;
- })
- .cast(['', 1, 0, 4, false, null]); // => ['', 1, 0, 4, false]
- ```
tuple
- ``` js
- import { tuple, string, number, InferType } from 'yup';
- let schema = tuple([
- string().label('name'),
- number().label('age').positive().integer(),
- ]);
- await schema.validate(['James', 3]); // ['James', 3]
- await schema.validate(['James', -24]); // => ValidationError: age must be a positive number
- InferType<typeof schema> // [string, number] | undefined
- ```
object
- ``` js
- yup.object({
- name: string().required(),
- age: number().required().positive().integer(),
- email: string().email(),
- website: string().url(),
- });
- ```
Object schema defaults
- ``` js
- const schema = object({
- name: string().default(''),
- });
- schema.default(); // -> { name: '' }
- ```
- ``` js
- const schema = object({
- id: string().required(),
- names: object({
- first: string().required(),
- }),
- });
- schema.isValid({ id: 1 }); // false! names.first is required
- ```
{ id: '1', names: { first: undefined }}
- ```ts
- object({
- a: string(),
- b: number(),
- }).shape({
- b: string(),
- c: number(),
- });
- ```
- ```ts
- object({
- a: string(),
- b: string(),
- c: number(),
- });
- ```
object.json(): this
object.concat(schemaB: ObjectSchema): ObjectSchema
object.pick(keys: string[]): Schema
- ``` js
- const person = object({
- age: number().default(30).required(),
- name: string().default('pat').required(),
- color: string().default('red').required(),
- });
- const nameAndAge = person.pick(['name', 'age']);
- nameAndAge.getDefault(); // => { age: 30, name: 'pat'}
- ```
object.omit(keys: string[]): Schema
- ``` js
- const person = object({
- age: number().default(30).required(),
- name: string().default('pat').required(),
- color: string().default('red').required(),
- });
- const nameAndAge = person.omit(['color']);
- nameAndAge.getDefault(); // => { age: 30, name: 'pat'}
- ```
object.from(fromKey: string, toKey: string, alias: boolean = false): this
- ``` js
- let schema = object({
- myProp: mixed(),
- Other: mixed(),
- })
- .from('prop', 'myProp')
- .from('other', 'Other', true);
- schema.cast({ prop: 5, other: 6 }); // => { myProp: 5, other: 6, Other: 6 }
- ```