io-ts
By Giulio Canti and contributors
io-ts is a runtime type system for TypeScript that lets developers define codecs describing both a value's shape and how to validate and decode it from unknown input such as JSON. Each codec doubles as a TypeScript type via type inference,…
Definition
io-ts is a runtime type system for TypeScript that lets developers define codecs describing both a value's shape and how to validate and decode it from unknown input such as JSON. Each codec doubles as a TypeScript type via type inference, so the compile-time type and the runtime validation logic stay in sync by construction. Built on functional programming primitives from the fp-ts library, io-ts is aimed at codebases that already embrace a functional style.
Overview
io-ts exists to close a well-known gap in TypeScript: the type system disappears at runtime, so data arriving from outside the program, an API response, form input, or a config file, has no guarantee of matching its declared TypeScript type unless something actually checks it. io-ts addresses this by making the runtime check the single source of truth: you define a codec once, and TypeScript's static type is derived from it automatically, rather than writing a type and a separate validator that could drift apart. Mechanically, an io-ts codec is an object with `encode` and `decode` functions, where decoding takes an `unknown` value and returns either a validated, typed result or a description of what went wrong, modeled using the `Either` type from the fp-ts functional programming library rather than throwing exceptions or returning null. Codecs for primitives, objects, arrays, unions, and intersections combine to build up codecs for arbitrarily complex shapes, and TypeScript's `type-of` inference (`t.TypeOf<typeof MyCodec>`) extracts the corresponding static type directly from a codec definition. io-ts's tight coupling to fp-ts and its `Either`-based error handling sets it apart from more mainstream TypeScript validation libraries like Zod, which achieve similar type-inference goals with a more conventional, promise- and exception-friendly API that does not require adopting functional programming idioms. This makes io-ts considerably more powerful for teams already writing in a functional style, since codecs compose using the same combinators as the rest of an fp-ts codebase, but it raises the learning curve substantially for teams unfamiliar with functional error handling. In practice, io-ts is used in TypeScript codebases that have already standardized on fp-ts for domain logic, where validating and decoding external data using the same `Either`-based patterns as the rest of the application keeps the codebase stylistically consistent. It is common in domains like finance and distributed systems where teams value io-ts's precise, exhaustive handling of decoding failures. The cost of that power is accessibility: io-ts's API, centered on `Either`, functional combinators, and fp-ts conventions, is noticeably harder to pick up than schema libraries with a more object-oriented or fluent chaining style, and it pulls in fp-ts as a real dependency rather than being fully self-contained. Teams not already committed to functional programming in TypeScript typically find Zod's more conventional API delivers similar runtime-type-safety benefits with a much shorter ramp-up time, reserving io-ts for codebases where fp-ts is already the established idiom. That said, teams that make the investment often report that io-ts's exhaustive, composable error reporting scales better to very large, deeply nested data models than more ad hoc error-handling styles, which is part of why it persists in domains with unusually strict correctness requirements.
Key Features
- Codecs combine runtime validation with automatic TypeScript type inference
- Decoding returns an Either type rather than throwing or returning null
- Built directly on fp-ts functional programming primitives and combinators
- Composable codecs for objects, unions, intersections, and arrays
- Encode and decode functions support both parsing and serialization
- Ensures static types and runtime checks cannot drift apart