Introduction

Quick Start

Install the package + Vite plugin and generate your first validator from a TypeScript type.

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
RunTypes is the only thing that ends up in your runtime bundle, and it carries no dependencies. @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',
    }),
  ],
});
There's no special 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.

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

Copyright © 2026