# Migrate from io-ts

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

Migrating from [io-ts](https://github.com/gcanti/io-ts) to Valibot is straightforward in most cases since both libraries build schemas by composing small functions. A big difference is that Valibot does not depend on fp-ts. Instead of working with `Either` and functional combinators, you work with plain result objects. 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 io-ts imports to Valibot's and replace all occurrences of `t.` with `v.`. The imports from fp-ts are no longer needed.

```ts
// Change this
import * as t from 'io-ts';
import { isRight } from 'fp-ts/Either';
const Schema = t.type({ key: t.string });

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

## Restructure code

Instead of calling `.decode` on the codec, you pass the schema as the first argument to [`safeParse`](/api/safeParse.md). Where io-ts returns an `Either` that you inspect with `isRight` or `fold`, Valibot returns a result object with a `success` property that narrows the type when checked.

```ts
// Change this
const result = Schema.decode(input);
if (isRight(result)) {
  console.log(result.right);
} else {
  console.log(PathReporter.report(result));
}

// To this
const result = v.safeParse(Schema, input);
if (result.success) {
  console.log(result.output);
} else {
  console.log(v.summarize(result.issues));
}
```

If you prefer an exception to be thrown on invalid input, use [`parse`](/api/parse.md) instead. The `.is` method of a codec maps directly to the [`is`](/api/is.md) method.

```ts
// Change this
if (Schema.is(input)) {
  // input is typed
}

// To this
if (v.is(Schema, input)) {
  // input is typed
}
```

To further validate a value, io-ts uses `t.brand` or `t.refinement`. In Valibot you use [pipelines](/guides/pipelines.md) instead. This is a function that starts with a schema and is followed by up to 19 validation or transformation actions.

```ts
// Change this
const Positive = t.brand(
  t.number,
  (input): input is t.Branded<number, PositiveBrand> => input > 0,
  'Positive'
);

// To this
const Positive = v.pipe(
  v.number(),
  v.check((input) => input > 0),
  v.brand('Positive')
);
```

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.

## Optional properties

io-ts has no dedicated way to mark a single property as optional. The common workaround is an intersection of `t.type` and `t.partial`. In Valibot, you simply wrap optional entries with [`optional`](/api/optional.md), which usually reduces the schema to a single object.

```ts
// Change this
const Schema = t.intersection([
  t.type({ name: t.string }),
  t.partial({ age: t.number }),
]);

// To this
const Schema = v.looseObject({
  name: v.string(),
  age: v.optional(v.number()),
});
```

## Change names

Many of the names are the same as in io-ts. However, there are some exceptions. The following table shows all names that have changed.

| io-ts           | Valibot                                                                                      |
| --------------- | -------------------------------------------------------------------------------------------- |
| `brand`         | [`check`](/api/check.md) with [`brand`](/api/brand.md)         |
| `decode`        | [`safeParse`](/api/safeParse.md), [`parse`](/api/parse.md)     |
| `exact`         | [`object`](/api/object.md)                                                    |
| `Int`           | [`number`](/api/number.md) with [`integer`](/api/integer.md)   |
| `interface`     | [`looseObject`](/api/looseObject.md)                                          |
| `intersection`  | [`intersect`](/api/intersect.md)                                              |
| `keyof`         | [`picklist`](/api/picklist.md)                                                |
| `OutputOf`      | [`InferInput`](/api/InferInput.md)                                            |
| `PathReporter`  | [`summarize`](/api/summarize.md), [`flatten`](/api/flatten.md) |
| `readonlyArray` | [`array`](/api/array.md) with [`readonly`](/api/readonly.md)   |
| `recursion`     | [`lazy`](/api/lazy.md)                                                        |
| `refinement`    | [`check`](/api/check.md)                                                      |
| `strict`        | [`object`](/api/object.md)                                                    |
| `type`          | [`looseObject`](/api/looseObject.md)                                          |
| `TypeOf`        | [`InferOutput`](/api/InferOutput.md)                                          |
| `UnknownArray`  | [`array`](/api/array.md) with [`unknown`](/api/unknown.md)     |
| `UnknownRecord` | [`record`](/api/record.md) with [`unknown`](/api/unknown.md)   |

## Other details

Below are some more details that may be helpful when migrating from io-ts to Valibot.

### Unknown object keys

io-ts keeps unknown keys when validating objects with `t.type` and removes them when the codec is wrapped with `t.exact` or created with `t.strict`. Valibot's [`looseObject`](/api/looseObject.md) and [`object`](/api/object.md) schemas match these two behaviors. In addition, [`strictObject`](/api/strictObject.md) allows you to reject unknown keys entirely, which io-ts does not support out of the box. See the [objects](/guides/objects.md) guide for more details.

```ts
// Change this
const Schema = t.strict({ key: t.string });

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

### Custom codecs

Custom codecs created with `new t.Type` are usually replaced by a pipeline that validates the input and then transforms it. Note that Valibot only covers the decode direction. There is no equivalent to `.encode`, as Valibot does not support transforming in the reverse direction.

```ts
// Change this
const DateFromString = new t.Type<Date, string, unknown>(
  'DateFromString',
  (input): input is Date => input instanceof Date,
  (input, context) => {
    if (typeof input !== 'string') return t.failure(input, context);
    const date = new Date(input);
    return isNaN(date.getTime()) ? t.failure(input, context) : t.success(date);
  },
  (date) => date.toISOString()
);

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

### Async validation

The core of io-ts is synchronous. Valibot additionally provides dedicated async functions like [`pipeAsync`](/api/pipeAsync.md) and [`checkAsync`](/api/checkAsync.md) that allow you to run asynchronous logic, such as database checks, as part of your schema. See the [async guide](/guides/async-validation.md) for more details.
