io-ts: Runtime Type System for TypeScript
io-ts is a TypeScript library providing a robust runtime type system, designed for decoding and encoding data at the boundaries of your application. It allows developers to define types once using its codec combinators, which then serve for both static type checking during development and dynamic validation of external data at runtime. This approach significantly reduces common errors when integrating with uncertain data sources like API responses, user input, or configuration files. The current stable version is 2.2.22. The library maintains an active release cadence with frequent patch updates, often introducing and refining "experimental" features in dedicated modules while ensuring a stable core. A key differentiator is its deep integration with `fp-ts`, leveraging functional programming paradigms for highly composable type definitions and comprehensive error handling, promoting a type-safe and resilient approach to data processing.
Common errors
-
Error: Cannot find module 'io-ts' or its corresponding type declarations.
cause Attempting to use `require()` with an ESM-first module, incorrect `moduleResolution` in `tsconfig.json`, or an outdated Node.js environment/configuration.fixEnsure you are using `import` statements (e.g., `import * as t from 'io-ts';`). Verify `tsconfig.json` has `"module": "NodeNext"` or `"ESNext"` and `"moduleResolution": "NodeNext"` or `"Bundler"`. For Node.js, ensure `"type": "module"` is set in your `package.json` if using ESM. -
TypeError: Cannot read properties of undefined (reading 'pipe') or TS2307: Cannot find module 'fp-ts/function'.
cause The `fp-ts` library, which `io-ts` depends on for many functional utilities, is missing or incorrectly installed.fixInstall `fp-ts` as a dependency: `npm install fp-ts`. Also, ensure correct import paths like `import { pipe } from 'fp-ts/function';`. -
TypeError: Invalid value undefined supplied to /0: { type: number }cause The input data passed to an `io-ts` decoder does not conform to the defined schema. This specific error indicates `undefined` was provided where a `number` was expected at a particular path in the data structure.fixExamine the `PathReporter.report()` output for detailed error messages indicating exactly where the type mismatch occurred. Adjust the input data to match the expected `io-ts` codec schema.
Warnings
- breaking The `sum` combinator underwent a breaking change in `io-ts@2.2.12`. For non-`string` tag values, the respective key in the sum object must now be enclosed in brackets (e.g., `D.sum('type')({ [1]: ... })`).
- deprecated The `type` and `fromType` combinators were deprecated in `io-ts@2.2.15` in favor of `struct` and `fromStruct` respectively. While still functional, it's recommended to migrate to the newer alternatives.
- gotcha Experimental modules (e.g., `io-ts/Decoder`, `io-ts/Codec`, `io-ts/Encoder`) introduced in `io-ts@2.2+` are subject to change without notice and may be backward-incompatible with stable features. Use them with caution in production environments.
- gotcha `fp-ts` is a mandatory peer dependency for `io-ts`. Installation of `io-ts` alone will lead to runtime errors or TypeScript compilation issues due to missing functional programming utilities (e.g., `pipe`, `Either`).
- gotcha `io-ts` had specific bug fixes related to `typescript@4.8` errors in version `2.2.18`. Older versions might experience compilation issues with newer TypeScript releases.
Install
-
npm install io-ts -
yarn add io-ts -
pnpm add io-ts
Imports
- t
const t = require('io-ts');import * as t from 'io-ts';
- PathReporter
import { PathReporter } from 'io-ts';import { PathReporter } from 'io-ts/PathReporter'; - D
import * as D from 'io-ts';
import * as D from 'io-ts/Decoder';
Quickstart
import * as t from 'io-ts';
import { pipe } from 'fp-ts/function';
import * as E from 'fp-ts/Either';
import { PathReporter } from 'io-ts/PathReporter';
// 1. Define a runtime type using io-ts codecs
const User = t.type({
id: t.number,
name: t.string,
email: t.string,
isAdmin: t.boolean,
tags: t.array(t.string),
});
// Infer the TypeScript type from the runtime type
type User = t.TypeOf<typeof User>;
// 2. Data to decode
const validUserData = {
id: 123,
name: 'Alice',
email: 'alice@example.com',
isAdmin: false,
tags: ['developer', 'typescript'],
};
const invalidUserData = {
id: 'abc', // Invalid type
name: 'Bob',
email: 'bob@example.com',
isAdmin: 'no', // Invalid type
tags: [123, 'tester'], // Mixed types
};
// 3. Decode valid data
pipe(
User.decode(validUserData),
E.fold(
(errors) => console.error('Failed to decode valid data:', PathReporter.report(E.left(errors))),
(user) => {
console.log('Successfully decoded valid user:', user);
const _user: User = user; // Type-check confirms 'user' is of type User
console.log('Is Alice an admin?', _user.isAdmin);
}
)
);
// 4. Decode invalid data and report errors
pipe(
User.decode(invalidUserData),
E.fold(
(errors) => console.error('Failed to decode invalid data:', PathReporter.report(E.left(errors))),
(user) => console.log('Unexpectedly decoded invalid user:', user)
)
);
// Example with a nullable/optional field
const OptionalUser = t.partial({
phone: t.string,
});
const UserWithOptionalPhone = t.intersection([User, OptionalUser]);
const userWithPhone = {
id: 456,
name: 'Charlie',
email: 'charlie@example.com',
isAdmin: true,
tags: ['lead'],
phone: '555-1234'
};
pipe(
UserWithOptionalPhone.decode(userWithPhone),
E.fold(
(errors) => console.log('Failed to decode user with phone:', PathReporter.report(E.left(errors))),
(user) => console.log('Decoded user with phone:', user)
)
);