# Migrate from Joi

> This document is the Markdown version of [valibot.dev/guides/migrate-from-joi/](https://valibot.dev/guides/migrate-from-joi/). For the complete documentation index, see [llms.txt](https://valibot.dev/llms.txt).

Migrating from [Joi](https://joi.dev/) to Valibot is straightforward in most cases since both APIs share the same basic concepts. Beyond a much smaller bundle size, one of the biggest benefits of migrating is that Valibot infers the TypeScript type of your data from your schema, which eliminates the need to maintain separate type definitions. 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](/guides/installation.md) Valibot is to update your imports. Just change your Joi imports to Valibot's and replace all occurrences of `Joi.` with `v.`.

```ts
// Change this
import Joi from 'joi';
const Schema = Joi.object({ key: Joi.string().required() });

// To this
import * as v from 'valibot';
const Schema = v.object({ key: v.string() });
```

## Restructure code

One of the biggest differences between Joi and Valibot is the way you further validate a given type. In Joi, you chain methods like `.email` and `.max`. In Valibot you use [pipelines](/guides/pipelines.md) 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.

```ts
// Change this
const Schema = Joi.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` 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. Where Joi returns an object with `value` and `error`, [`safeParse`](/api/safeParse.md) returns a result object with `output` and `issues`.

```ts
// Change this
const { value, error } = Joi.string().validate('foo');

// To this
const result = v.safeParse(v.string(), 'foo');
if (result.success) {
  console.log(result.output);
} else {
  console.log(result.issues);
}
```

If you prefer an exception to be thrown on invalid input, as with `Joi.attempt` or `Joi.assert`, use [`parse`](/api/parse.md) instead.

```ts
// Change this
const value = Joi.attempt('foo', Joi.string());

// To this
const value = v.parse(v.string(), 'foo');
```

We recommend that you read our [mental model](/guides/mental-model.md) guide to understand how the individual functions of Valibot's modular API work together.

## Infer types

Joi does not infer TypeScript types from your schemas, which usually forces you to maintain separate type definitions. With Valibot, you can remove these duplicate definitions and infer the types directly from your schemas. See the [infer types](/guides/infer-types.md) guide for more details.

```ts
const UserSchema = v.object({
  name: v.string(),
  email: v.pipe(v.string(), v.email()),
});

type User = v.InferOutput<typeof UserSchema>;
// { name: string; email: string }
```

## Required by default

Joi 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`](/api/optional.md), [`nullable`](/api/nullable.md) or [`nullish`](/api/nullish.md).

```ts
// Change this
const Schema = Joi.object({
  name: Joi.string().required(),
  email: Joi.string(),
});

// To this
const Schema = v.object({
  name: v.string(),
  email: v.optional(v.string()),
});
```

There is one detail to watch out for. Joi's string schema rejects empty strings by default. If you rely on this behavior, add the [`nonEmpty`](/api/nonEmpty.md) action to your pipeline.

```ts
// Change this
const Schema = Joi.string().required();

// To this
const Schema = v.pipe(v.string(), v.nonEmpty());
```

## No implicit type conversion

By default, Joi converts values to the expected type before validating them. For example, `Joi.number()` accepts the string `'24'` and converts it to the number `24`, and `Joi.date()` converts ISO strings to `Date` objects. Valibot never changes your data implicitly. If you rely on type conversion, use a pipeline with an explicit [`transform`](/api/transform.md) action or one of the dedicated transformation actions like [`toNumber`](/api/toNumber.md) or [`toDate`](/api/toDate.md). This forces you to explicitly define the input, resulting in safer code.

```ts
// Change this
const NumberSchema = Joi.number();

// To this
const NumberSchema = v.pipe(v.string(), v.toNumber());
```

```ts
// Change this
const DateSchema = Joi.date();

// To this
const DateSchema = v.pipe(v.string(), v.toDate());
```

Keep in mind that [`toNumber`](/api/toNumber.md) behaves like JavaScript's `Number` function and therefore converts empty strings to `0`, and that [`toDate`](/api/toDate.md) accepts any string that the `Date` constructor can parse. For stricter validation, we recommend adding actions like [`decimal`](/api/decimal.md) or [`isoTimestamp`](/api/isoTimestamp.md) to validate the formatting of the string before converting it.

```ts
const NumberSchema = v.pipe(v.string(), v.decimal(), v.toNumber());
const DateSchema = v.pipe(v.string(), v.isoTimestamp(), v.toDate());
```

## Collect all issues

Another difference is that Joi stops validation after the first error by default. Valibot collects all issues instead. If you prefer Joi's behavior for performance reasons, you can pass a configuration object with `abortEarly: true` as the third argument to [`parse`](/api/parse.md) or [`safeParse`](/api/safeParse.md).

```ts
// Change this
const { error } = Schema.validate(input);

// To this
const result = v.safeParse(Schema, input, { abortEarly: true });
```

## Change names

Most of the names are similar to Joi. However, there are some exceptions. The following table shows all names that have changed.

| Joi             | Valibot                                                                                                                                                 |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `allow(null)`   | [`nullable`](/api/nullable.md)                                                                                                           |
| `alphanum`      | [`regex`](/api/regex.md)                                                                                                                 |
| `alternatives`  | [`union`](/api/union.md), [`variant`](/api/variant.md)                                                                    |
| `append`        | [Object merging](/guides/intersections.md#merge-objects)                                                                                      |
| `assert`        | [`parse`](/api/parse.md)                                                                                                                 |
| `attempt`       | [`parse`](/api/parse.md)                                                                                                                 |
| `concat`        | [Object merging](/guides/intersections.md#merge-objects)                                                                                      |
| `custom`        | [`check`](/api/check.md), [`rawCheck`](/api/rawCheck.md), [`rawTransform`](/api/rawTransform.md)           |
| `default`       | [`optional`](/api/optional.md)                                                                                                           |
| `equal`         | [`literal`](/api/literal.md), [`picklist`](/api/picklist.md)                                                              |
| `external`      | [`checkAsync`](/api/checkAsync.md)                                                                                                       |
| `forbidden`     | [`optional`](/api/optional.md) with [`never`](/api/never.md)                                                              |
| `greater`       | [`gtValue`](/api/gtValue.md)                                                                                                             |
| `guid`          | [`uuid`](/api/uuid.md)                                                                                                                   |
| `hex`           | [`hexadecimal`](/api/hexadecimal.md)                                                                                                     |
| `invalid`       | [`notValue`](/api/notValue.md), [`notValues`](/api/notValues.md)                                                          |
| `isoDate`       | [`isoDate`](/api/isoDate.md), [`isoDateTime`](/api/isoDateTime.md), [`isoTimestamp`](/api/isoTimestamp.md) |
| `items`         | Item argument of [`array`](/api/array.md)                                                                                                |
| `keys`          | [Object merging](/guides/intersections.md#merge-objects)                                                                                      |
| `length`        | [`length`](/api/length.md), [`size`](/api/size.md), [`entries`](/api/entries.md)                           |
| `less`          | [`ltValue`](/api/ltValue.md)                                                                                                             |
| `link`          | [`lazy`](/api/lazy.md)                                                                                                                   |
| `lowercase`     | [`toLowerCase`](/api/toLowerCase.md)                                                                                                     |
| `max`           | [`maxLength`](/api/maxLength.md), [`maxValue`](/api/maxValue.md), [`maxEntries`](/api/maxEntries.md)       |
| `min`           | [`minLength`](/api/minLength.md), [`minValue`](/api/minValue.md), [`minEntries`](/api/minEntries.md)       |
| `multiple`      | [`multipleOf`](/api/multipleOf.md)                                                                                                       |
| `negative`      | [`ltValue`](/api/ltValue.md)                                                                                                             |
| `ordered`       | [`tuple`](/api/tuple.md), [`tupleWithRest`](/api/tupleWithRest.md)                                                        |
| `pattern`       | [`regex`](/api/regex.md), [`record`](/api/record.md), [`objectWithRest`](/api/objectWithRest.md)           |
| `positive`      | [`gtValue`](/api/gtValue.md)                                                                                                             |
| `try`           | [`union`](/api/union.md), [`variant`](/api/variant.md)                                                                    |
| `unique`        | [`checkItems`](/api/checkItems.md)                                                                                                       |
| `unknown`       | [`looseObject`](/api/looseObject.md)                                                                                                     |
| `uppercase`     | [`toUpperCase`](/api/toUpperCase.md)                                                                                                     |
| `uri`           | [`url`](/api/url.md)                                                                                                                     |
| `valid`         | [`literal`](/api/literal.md), [`picklist`](/api/picklist.md)                                                              |
| `validate`      | [`parse`](/api/parse.md), [`safeParse`](/api/safeParse.md)                                                                |
| `validateAsync` | [`parseAsync`](/api/parseAsync.md), [`safeParseAsync`](/api/safeParseAsync.md)                                            |

## Other details

Below are some more details that may be helpful when migrating from Joi to Valibot.

### Unknown object keys

By default, Joi rejects unknown keys when validating objects. This matches Valibot's [`strictObject`](/api/strictObject.md) schema. If you use `.unknown()` to keep unknown keys, use [`looseObject`](/api/looseObject.md) instead. If you use the `stripUnknown` option to remove them, [`object`](/api/object.md) is the right choice. See the [objects](/guides/objects.md) guide for more details.

```ts
// Change this
const ObjectSchema = Joi.object({ key: Joi.string().required() });

// To this
const ObjectSchema = v.strictObject({ key: v.string() });
```

### Cross-field validation

Joi uses `Joi.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`](/api/forward.md) and [`partialCheck`](/api/partialCheck.md) instead.

```ts
// Change this
const RegisterSchema = Joi.object({
  password: Joi.string().required(),
  confirmPassword: Joi.string()
    .required()
    .valid(Joi.ref('password'))
    .messages({ 'any.only': '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 Joi's `.when()` method. For discriminated unions, we recommend [`variant`](/api/variant.md). For other conditional validations, you can use [`check`](/api/check.md) or [`rawCheck`](/api/rawCheck.md) on the parent object, or select the schema dynamically with [`lazy`](/api/lazy.md), which receives the current input as an argument.

### Error messages

Instead of `.messages()`, `.message()` and `.label()`, you pass a single string as the first argument to schemas and as the last argument to actions. See the [quick start](/guides/quick-start.md#error-messages) and [internationalization](/guides/internationalization.md) guide for more details.

```ts
// Change this
const Schema = Joi.string().min(10).messages({
  'string.base': 'Must be a string',
  'string.min': 'String is too short',
});

// To this
const Schema = v.pipe(
  v.string('Must be a string'),
  v.minLength(10, 'String is too short')
);
```

### Async validation

Joi's `.external()` method allows asynchronous validation rules. Valibot provides dedicated async functions like [`parseAsync`](/api/parseAsync.md), [`pipeAsync`](/api/pipeAsync.md) and [`checkAsync`](/api/checkAsync.md) for this purpose. See the [async guide](/guides/async-validation.md) for more details.

```ts
// Change this
const Schema = Joi.string().external(async (value) => {
  if (await isUsernameTaken(value)) {
    throw new Error('Username is already taken');
  }
  return value;
});

// To this
const Schema = v.pipeAsync(
  v.string(),
  v.checkAsync(
    async (input) => !(await isUsernameTaken(input)),
    'Username is already taken'
  )
);
```
