Migrate from Yup
Migrating from Yup to Valibot is straightforward in most cases since both APIs share the same basic concepts. The following guide will help you migrate step by step and also point out important differences.
Replace imports
The first thing to do after installing Valibot is to update your imports. Just change your Yup imports to Valibot's and replace all occurrences of yup. with v..
// Change this
import * as yup from 'yup';
const Schema = yup.object({ key: yup.string().required() });
// To this
import * as v from 'valibot';
const Schema = v.object({ key: v.string() });Restructure code
One of the biggest differences between Yup and Valibot is the way you further validate a given type. In Yup, you chain methods like .email and .max. In Valibot you use pipelines to do the same thing. This is a function that starts with a schema and is followed by up to 19 validation or transformation actions.
// Change this
const Schema = yup.string().email().max(30);
// To this
const Schema = v.pipe(v.string(), v.email(), v.maxLength(30));Due to the modular design of Valibot, also all other methods like .validate or .isValid have to be used a little bit differently. Instead of chaining them, you usually pass the schema as the first argument and move any existing arguments one position to the right. Note that Yup's .validate method is asynchronous by default, whereas parse is synchronous.
// Change this
const value = yup.string().validateSync('foo');
// To this
const value = v.parse(v.string(), 'foo');We recommend that you read our mental model guide to understand how the individual functions of Valibot's modular API work together.
Required by default
Yup schemas are optional by default. To reject undefined, you have to append .required() to each schema. Valibot works the other way around. Every schema is required by default, and you explicitly mark schemas as optional by wrapping them with optional, nullable or nullish.
// Change this
const Schema = yup.object({
name: yup.string().required(),
email: yup.string(),
});
// To this
const Schema = v.object({
name: v.string(),
email: v.optional(v.string()),
});There is one detail to watch out for. For string schemas, Yup's .required() also rejects empty strings. If you rely on this behavior, add the nonEmpty action to your pipeline.
// Change this
const Schema = yup.string().required();
// To this
const Schema = v.pipe(v.string(), v.nonEmpty());No implicit type coercion
Before validating a value, Yup casts it to the expected type. For example, yup.number() accepts the string '24' and converts it to the number 24. This even applies to .isValid checks. Valibot never changes your data implicitly. If you rely on type coercion, use a pipeline with an explicit transform action or one of the dedicated transformation actions like toNumber or toDate. This forces you to explicitly define the input, resulting in safer code.
// Change this
const NumberSchema = yup.number();
// To this
const NumberSchema = v.pipe(v.string(), v.toNumber());The same applies to dates. Yup's .cast method converts ISO strings to Date objects. In Valibot, you define this conversion explicitly.
// Change this
const DateSchema = yup.date();
// To this
const DateSchema = v.pipe(v.string(), v.toDate());Keep in mind that toNumber behaves like JavaScript's Number function and therefore converts empty strings to 0, and that toDate accepts any string that the Date constructor can parse. For stricter validation, we recommend adding actions like decimal or isoTimestamp to validate the formatting of the string before converting it.
const NumberSchema = v.pipe(v.string(), v.decimal(), v.toNumber());
const DateSchema = v.pipe(v.string(), v.isoTimestamp(), v.toDate());Change names
Most of the names are the same as in Yup. However, there are some exceptions. The following table shows all names that have changed.
| Yup | Valibot |
|---|---|
bool | boolean |
concat | Object merging |
default | optional |
InferType | InferOutput |
isValid | is |
json | parseJson |
lessThan | ltValue |
matches | regex |
max | maxLength, maxValue |
min | minLength, minValue |
mixed | any, unknown |
moreThan | gtValue |
negative | ltValue |
noUnknown | strictObject |
notOneOf | notValues |
notRequired | nullish |
of | Item argument of array |
oneOf | picklist |
positive | gtValue |
shape | entries |
strip | omit |
test | check, rawCheck |
typeError | Error message argument of schema |
validate | parseAsync, safeParseAsync |
validateSync | parse, safeParse |
Other details
Below are some more details that may be helpful when migrating from Yup to Valibot.
Unknown object keys
By default, Yup keeps unknown keys when validating objects. Valibot's object schema removes them instead. If you rely on Yup's behavior, use looseObject. If you use the stripUnknown option, object is the right choice. To reject unknown keys, as with .noUnknown() in combination with .strict(), use strictObject. See the objects guide for more details.
// Change this
const ObjectSchema = yup.object({ key: yup.string().required() });
// To this
const ObjectSchema = v.looseObject({ key: v.string() });Cross-field validation
Yup uses ref to reference the value of another field, for example to check that two passwords match. In Valibot, you use a pipeline on the object schema with forward and partialCheck instead.
// Change this
const RegisterSchema = yup.object({
password: yup.string().required(),
confirmPassword: yup
.string()
.required()
.oneOf([yup.ref('password')], 'The two passwords do not match.'),
});
// To this
const RegisterSchema = v.pipe(
v.object({
password: v.pipe(v.string(), v.nonEmpty()),
confirmPassword: v.pipe(v.string(), v.nonEmpty()),
}),
v.forward(
v.partialCheck(
[['password'], ['confirmPassword']],
(input) => input.password === input.confirmPassword,
'The two passwords do not match.'
),
['confirmPassword']
)
);Conditional schemas
There is no direct equivalent to Yup's .when() method. For discriminated unions, we recommend variant. For other conditional validations, you can use check or rawCheck on the parent object, or select the schema dynamically with lazy, which receives the current input as an argument.
Error messages
Instead of .typeError() and per-validation message arguments, you pass a single string as the first argument to schemas and as the last argument to actions. See the quick start guide for more details.
// Change this
const Schema = yup
.number()
.typeError('Must be a number')
.min(10, 'Number is too small');
// To this
const Schema = v.pipe(
v.number('Must be a number'),
v.minValue(10, 'Number is too small')
);Async validation
In Yup, .validate is always asynchronous. Valibot is synchronous by default and provides dedicated async functions like parseAsync, pipeAsync and checkAsync for schemas that require asynchronous logic, such as database checks. See the async guide for more details.