Schema
Schema
is the abstract base class that all schema type inherit from. It provides a number of base methods and properties
to all other schema types.
Note: unless you are creating a custom schema type, Schema should never be used directly. For unknown/any types use
mixed()
Schema.clone(): Schema
​
Creates a deep copy of the schema. Clone is used internally to return a new schema with every schema state change.
Schema.label(label: string): Schema
​
Overrides the key name which is used in error messages.
Schema.meta(metadata: object): Schema
​
Adds to a metadata object, useful for storing data with a schema, that doesn't belong the cast object itself.
Schema.describe(options?: ResolveOptions): SchemaDescription
​
Collects schema details (like meta, labels, and active tests) into a serializable description object.
const schema = object({
name: string().required(),
});
const description = schema.describe();
For schema with dynamic components (references, lazy, or conditions), describe requires
more context to accurately return the schema description. In these cases provide options
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 } });
And below is are the description types, which differ a bit depending on the schema type.
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
​
Creates a new instance of the schema by combining two schemas. Only schemas of the same type can be concatenated.
concat
is not a "merge" function in the sense that all settings from the provided schema, override ones in the
base, including type, presence and nullability.
mixed<string>().defined().concat(mixed<number>().nullable());
// produces the equivalent to:
mixed<number>().defined().nullable();
Schema.validate(value: any, options?: object): Promise<InferType<Schema>, ValidationError>
​
Returns the parses and validates an input value, returning the parsed value or throwing an error. This method is asynchronous and returns a Promise object, that is fulfilled with the value, or rejected
with a ValidationError
.
value = await schema.validate({ name: 'jimmy', age: 24 });
Provide options
to more specifically control the behavior of validate
.
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 preformed shallowly
recursive: boolean = true;
// External values that can be provided to validations and conditionals
context?: object;
}
Schema.validateSync(value: any, options?: object): InferType<Schema>
​
Runs validatations synchronously if possible and returns the resulting value,
or throws a ValidationError. Accepts all the same options as validate
.
Synchronous validation only works if there are no configured async tests, e.g tests that return a Promise. For instance this will work:
let schema = number().test(
'is-42',
"this isn't the number i want",
(value) => value != 42,
);
schema.validateSync(23); // throws ValidationError
however this will not:
let schema = number().test('is-42', "this isn't the number i want", (value) =>
Promise.resolve(value != 42),
);
schema.validateSync(42); // throws Error
Schema.validateAt(path: string, value: any, options?: object): Promise<InferType<Schema>, ValidationError>
​
Validate a deeply nested path within the schema. Similar to how reach
works,
but uses the resulting schema as the subject for validation.
Note! The
value
here is the root value relative to the starting schema, not the value at the nested path.
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.validateSyncAt(path: string, value: any, options?: object): InferType<Schema>
​
Same as validateAt
but synchronous.
Schema.isValid(value: any, options?: object): Promise<boolean>
​
Returns true
when the passed in value matches the schema. isValid
is asynchronous and returns a Promise object.
Takes the same options as validate()
.
Schema.isValidSync(value: any, options?: object): boolean
​
Synchronously returns true
when the passed in value matches the schema.
Takes the same options as validateSync()
and has the same caveats around async tests.
Schema.cast(value: any, options = {}): InferType<Schema>
​
Attempts to coerce the passed in value to a value that matches the schema. For example: '5'
will
cast to 5
when using the number()
type. Failed casts generally return null
, but may also
return results like NaN
and unexpected strings.
Provide options
to more specifically control the behavior of validate
.
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.isType(value: any): value is InferType<Schema>
​
Runs a type check against the passed in value
. It returns true if it matches,
it does not cast the value. When nullable()
is set null
is considered a valid value of the type.
You should use isType
for all Schema type checks.
Schema.strict(enabled: boolean = false): Schema
​
Sets the strict
option to true
. Strict schemas skip coercion and transformation attempts,
validating the value "as is".
Schema.strip(enabled: boolean = true): Schema
​
Marks a schema to be removed from an output object. Only works as a nested schema.
let schema = object({
useThis: number(),
notThis: string().strip(),
});
schema.cast({ notThis: 'foo', useThis: 4 }); // => { useThis: 4 }
Schema with strip
enabled have an inferred type of never
, allowing them to be
removed from the overall type:
let schema = object({
useThis: number(),
notThis: string().strip(),
});
InferType<typeof schema>; /*
{
useThis?: number | undefined
}
*/
Schema.withMutation(builder: (current: Schema) => void): void
​
First the legally required Rich Hickey quote:
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?
withMutation
allows you to mutate the schema in place, instead of the default behavior which clones before each change. Generally this isn't necessary since the vast majority of schema changes happen during the initial
declaration, and only happen once over the lifetime of the schema, so performance isn't an issue.
However certain mutations do occur at cast/validation time, (such as conditional schema using when()
), or
when instantiating a schema object.
object()
.shape({ key: string() })
.withMutation((schema) => {
return arrayOfObjectTests.forEach((test) => {
schema.test(test);
});
});
Schema.default(value: any): Schema
​
Sets a default value to use when the value is undefined
.
Defaults are created after transformations are executed, but before validations, to help ensure that safe
defaults are specified. The default value will be cloned on each use, which can incur performance penalty
for objects and arrays. To avoid this overhead you can also pass a function that returns a new default.
Note that null
is considered a separate non-empty value.
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
​
Retrieve a previously set default value. getDefault
will resolve any conditions that may alter the default. Optionally pass options
with context
(for more info on context
see Schema.validate
).
Schema.nullable(): Schema
​
Indicates that null
is a valid value for the schema. Without nullable()
null
is treated as a different type and will fail Schema.isType()
checks.
const schema = number().nullable()
schema.cast(null); // null
InferType<typeof schema> // number | null
Schema.nonNullable(): Schema
​
The opposite of nullable
, removes null
from valid type values for the schema.
Schema are non nullable by default.
const schema = number().nonNullable()
schema.cast(null); // TypeError
InferType<typeof schema> // number
Schema.defined(): Schema
​
Require a value for the schema. All field values apart from undefined
meet this requirement.
const schema = string().defined()
schema.cast(undefined); // TypeError
InferType<typeof schema> // string
Schema.optional(): Schema
​
The opposite of defined()
allows undefined
values for the given type.
const schema = string().optional()
schema.cast(undefined); // undefined
InferType<typeof schema> // string | undefined
Schema.required(message?: string | function): Schema
​
Mark the schema as required, which will not allow undefined
or null
as a value. required
negates the effects of calling optional()
and nullable()
Watch out!
string().required
) works a little different and additionally prevents empty string values (''
) when required.
Schema.notRequired(): Schema
Alias: optional()
​
Mark the schema as not required. This is a shortcut for schema.nonNullable().defined()
;
Schema.typeError(message: string): Schema
​
Define an error message for failed type checks. The ${value}
and ${type}
interpolation can
be used in the message
argument.
Schema.oneOf(arrayOfValues: Array<any>, message?: string | function): Schema
Alias: equals
​
Only allow values from set of values. Values added are removed from any notOneOf
values if present.
The ${values}
interpolation can be used in the message
argument. If a ref or refs are provided,
the ${resolved}
interpolation can be used in the message argument to get the resolved values that were checked
at validation time.
Note that undefined
does not fail this validator, even when undefined
is not included in arrayOfValues
.
If you don't want undefined
to be a valid value, you can use Schema.required
.
let schema = yup.mixed().oneOf(['jimmy', 42]);
await schema.isValid(42); // => true
await schema.isValid('jimmy'); // => true
await schema.isValid(new Date()); // => false
Schema.notOneOf(arrayOfValues: Array<any>, message?: string | function)
​
Disallow values from a set of values. Values added are removed from oneOf
values if present.
The ${values}
interpolation can be used in the message
argument. If a ref or refs are provided,
the ${resolved}
interpolation can be used in the message argument to get the resolved values that were checked
at validation time.
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
​
Adjust the schema based on a sibling or sibling children fields. You can provide an object
literal where the key is
is value or a matcher function, then
provides the true schema and/or
otherwise
for the failure condition.
is
conditions are strictly compared (===
) if you want to use a different form of equality you
can provide a function like: is: (value) => value == true
.
You can also prefix properties with $
to specify a property that is dependent
on context
passed in by validate()
or cast
instead of the input value.
when
conditions are additive.
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 } });
You can also specify more than one dependent key, in which case each value will be spread as an argument.
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,
});
Alternatively you can provide a function that returns a schema, called with an array of values for each provided key the current schema.
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
​
Adds a test function to the validation chain. Tests are run after any object is cast. Many types have some tests built in, but you can create custom ones easily. In order to allow asynchronous custom validations all (or no) tests are run asynchronously. A consequence of this is that test execution order cannot be guaranteed.
All tests must provide a name
, an error message
and a validation function that must return
true
when the current value
is valid and false
or a ValidationError
otherwise.
To make a test async return a promise that resolves true
or false
or a ValidationError
.
For the message
argument you can provide a string which will interpolate certain values
if specified using the ${param}
syntax. By default all test messages are passed a path
value
which is valuable in nested schemas.
The test
function is called with the current value
. For more advanced validations you can
use the alternate signature to provide more options (see below):
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().test(
'is-jimmy',
'${path} is not Jimmy',
async (value, testContext) => (await fetch('/is-jimmy/' + value)).responseText === 'true',
});
await schema.isValid('jimmy'); // => true
await schema.isValid('john'); // => false
Test functions are called with a special context value, as the second argument, that exposes some useful metadata
and functions. For non arrow functions, the test context is also set as the function this
. Watch out, if you access
it via this
it won't work in an arrow function.
testContext.path
: the string path of the current validationtestContext.schema
: the resolved schema object that the test is running against.testContext.options
: theoptions
object that validate() or isValid() was called withtestContext.parent
: in the case of nested schema, this is the value of the parent objecttestContext.originalValue
: the original value that is being testedtestContext.createError(Object: { path: String, message: String, params: Object })
: create and return a validation error. Useful for dynamically setting thepath
,params
, or more likely, the errormessage
. If either option is omitted it will use the current path, or default message.
Schema.test(options: object): Schema
​
Alternative test(..)
signature. options
is an object containing some of the following options:
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;
}
In the case of mixing exclusive and non-exclusive tests the following logic is used. If a non-exclusive test is added to a schema with an exclusive test of the same name the exclusive test is removed and further tests of the same name will be stacked.
If an exclusive test is added to a schema with non-exclusive tests of the same name the previous tests are removed and further tests of the same name will replace each other.
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
​
Adds a transformation to the transform chain. Transformations are central to the casting process,
default transforms for each type coerce values to the specific type (as verified by isType()
). transforms are run before validations and only applied when the schema is not marked as strict
(the default). Some types have built in transformations.
Transformations are useful for arbitrarily altering how the object is cast, however, you should take care
not to mutate the passed in value. Transforms are run sequentially so each value
represents the
current state of the cast, you can use the originalValue
param if you need to work on the raw initial value.
let schema = string().transform((value, originalvalue) => {
return this.isType(value) && value !== null ? value.toUpperCase() : value;
});
schema.cast('jimmy'); // => 'JIMMY'
Each types will handle basic coercion of values to the proper type for you, but occasionally you may want to adjust or refine the default behavior. For example, if you wanted to use a different date parsing strategy than the default one you could do that with a transform.
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('');
});
};