Migrate from io-ts

Migrating from 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 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.

// 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. 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.

// 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 instead. The .is method of a codec maps directly to the is method.

// 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 instead. This is a function that starts with a schema and is followed by up to 19 validation or transformation actions.

// 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 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, which usually reduces the schema to a single object.

// 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-tsValibot
brandcheck with brand
decodesafeParse, parse
exactobject
Intnumber with integer
interfacelooseObject
intersectionintersect
keyofpicklist
OutputOfInferInput
PathReportersummarize, flatten
readonlyArrayarray with readonly
recursionlazy
refinementcheck
strictobject
typelooseObject
TypeOfInferOutput
UnknownArrayarray with unknown
UnknownRecordrecord with unknown

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 and object schemas match these two behaviors. In addition, strictObject allows you to reject unknown keys entirely, which io-ts does not support out of the box. See the objects guide for more details.

// 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.

// 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 and checkAsync that allow you to run asynchronous logic, such as database checks, as part of your schema. See the async guide for more details.

Contributors

Thanks to all the contributors who helped make this page better!

  • GitHub profile picture of @fabian-hiller

Partners

Thanks to our partners who support the project ideally and financially.

Sponsors

Thanks to our GitHub sponsors who support the project financially.

  • GitHub profile picture of @vasilii-kovalev
  • GitHub profile picture of @UpwayShop
  • GitHub profile picture of @ruiaraujo012
  • GitHub profile picture of @hyunbinseo
  • GitHub profile picture of @nickytonline
  • GitHub profile picture of @kibertoad
  • GitHub profile picture of @caegdeveloper
  • GitHub profile picture of @Thanaen
  • GitHub profile picture of @bmoyroud
  • GitHub profile picture of @ysknsid25
  • GitHub profile picture of @dslatkin