Quick Start
1. Install
You need two things: the runtime package and the Vite plugin that reads your types.
pnpm add @ts-runtypes/core
pnpm add -D @ts-runtypes/devtools
npm install @ts-runtypes/core
npm install -D @ts-runtypes/devtools
yarn add @ts-runtypes/core
yarn add -D @ts-runtypes/devtools
@ts-runtypes/devtools does its work at build time and ships nothing to production.2. Wire up the Vite plugin
Add the plugin to your vite.config.ts. It runs the Go binary that does the type reading.
import {defineConfig} from 'vite';
import runtypes from '@ts-runtypes/devtools/vite';
// vite.config.ts. Add the plugin. The resolver binary for your platform is
// installed automatically (an optional dependency) and resolved for you.
export default defineConfig({
plugins: [
runtypes({
tsconfig: 'tsconfig.json',
}),
],
});
tsconfig.json flag to enable. Point the plugin at your existing tsconfig.json and it reads your types directly. (If you're coming from an older reflection library, this is the step you can skip.)3. Write a type and validate it
That's the whole setup. Now write a normal TypeScript type and ask for a validator. The build generates it for you.
import {createValidateFn, createGetValidationErrorsFn} from '@ts-runtypes/core';
// 1. Write a normal type.
type User = {
id: number;
name: string;
email: string;
roles: ('admin' | 'user')[];
};
// 2. Ask for a validator. The build generates it from `User`.
const isUser = createValidateFn<User>();
// 3. Use it. This is a real, specialized function — no runtime reflection.
const maybeUser: unknown = JSON.parse('{"id":1,"name":"Ada","email":"ada@x.io","roles":["admin"]}');
if (isUser(maybeUser)) {
// maybeUser is narrowed to User here.
console.log(maybeUser.name);
}
// Want the WHY, not just a yes/no? Reach for the error reporter.
const getUserErrors = createGetValidationErrorsFn<User>();
getUserErrors({id: '1', name: 'Ada'}); // [{path: ['id'], ...}, ...]
export {isUser, getUserErrors};
isUser is a real, specialized function. No schema, no decorators, no reflection at runtime, just a function that knows exactly what a User is.
Other bundlers
Vite is the shortest path, but the same plugin ships for Rollup, Rolldown, webpack, Rspack and esbuild. Each one has its own entry point, and every entry takes the same options.
| Bundler | Import |
|---|---|
| Vite | @ts-runtypes/devtools/vite |
| Rollup | @ts-runtypes/devtools/rollup |
| Rolldown | @ts-runtypes/devtools/rolldown |
| webpack | @ts-runtypes/devtools/webpack |
| Rspack | @ts-runtypes/devtools/rspack |
| esbuild | @ts-runtypes/devtools/esbuild |
import runtypes from '@ts-runtypes/devtools/rollup';
// rollup.config.js. Same plugin and the same options as the Vite entry, imported
// from the Rollup entry point instead.
export default {
plugins: [
runtypes({
tsconfig: 'tsconfig.json',
}),
],
};
The rewrite is identical whatever bundler runs it, so your generated code does not change when you switch. The Vite entry adds hot reload on top, and for a tool that is not on the list, @ts-runtypes/devtools/unplugin exposes the framework-agnostic unplugin instance the others are built from.