Skip to content

LIVRValidation rules that are just data

Keep rules in code or build them at runtime, LIVR is the fastest JavaScript validator either way. TypeScript types inferred from the schema. Zero dependencies. Built on a cross-language specification and maintained since 2013.

schema plain data

{
  email: ['required', 'email'],
  age:   ['required', 'positive_integer'],
  role:  { one_of: ['admin', 'user'] }
}

input

{
  email:   'ann@example.com',
  age:     '42',
  role:    'admin',
  isAdmin: true
}

output Valid

{
  email: 'ann@example.com',
  age:   42,
  role:  'admin'
}

type InferFromSchema<typeof schema>

{ email: string; age: number; role?: 'admin' | 'user' }

Rules are just data

Your validation rules can live anywhere โ€‹

Zod, Valibot and Joi schemas are JavaScript code, so they stay inside one JavaScript codebase. A LIVR schema is a plain data structure: you can keep it in a JSON file, a database row or a CMS, build it in a form builder, or receive it from another service. The validator never executes it, so a schema from an untrusted source cannot run code.

signup.json

json
{
  "email": ["required", "email", "to_lc"],
  "password": ["required", { "min_length": 8 }],
  "plan": { "one_of": ["free", "pro"] },
  "referrer": { "nested_object": {
    "code": { "like": "^[A-Z0-9]{6}$" }
  }}
}

server.js

js
import LIVR from 'livr';

// From a file, a database or an API: it is just data
const rules = await db.getValidationRules('signup');
const validator = new LIVR.Validator(rules);

const user = validator.validate(req.body);
if (!user) {
  res.status(422).json(validator.getErrors());
  // { email: 'WRONG_EMAIL', password: 'TOO_SHORT' }
}

The same JSON works in the browser, in Node.js, and in the Perl, PHP, Python, Go, Java, Ruby, Erlang and other implementations. And because rules are data, a program can build them at runtime: per tenant, per form, per request. Creating a validator from them takes less than a microsecond, see below.

Why schemas as data โ†’

Incredible performance

The fastest, also when rules are built at runtime โ€‹

Most validators are fast only when a schema is compiled once, at startup. When rules come from a database, differ per tenant or are put together in a form builder, every call needs a new validator, and validators that compile schemas into JavaScript code become 100โ€“1000 times slower. LIVR builds a closure for every rule straight from the data, without generating code, and leads both benchmarks.

3.50M
validations per second of a form with rules fixed in code, on one core
0.7 ยตs
to create a validator from JSON rules and validate a form: rules built at runtime cost almost nothing
86ร—
faster than Zod when rules are built at runtime (949ร— faster than Ajv)
Rules fixed in codeThe validator is created once and reused
LIVR 3.50M operations per second on the simple form. Valibot 1.2ร— slower, fastest-validator 1.2ร— slower, Zod 1.2ร— slower, Ajv 1.4ร— slower, Joi 11ร— slower.
Rules built at runtimeFrom a database, per tenant, from a form builder: a new validator for every call
LIVR 1.38M operations per second on the simple form. Valibot 1.5ร— slower, Joi 33ร— slower, Zod 86ร— slower, fastest-validator 126ร— slower, Ajv 949ร— slower.

Node.js 26.8.2, AMD Ryzen 9 5950X, 2026-10-18. Absolute numbers depend on the hardware, compare the ratios.

All results and how they were measured โ†’

Type inference

Types straight from the schema โ€‹

Add as const to a schema in your code, and InferFromSchema gives you the type of the validated data: required and optional fields, literal unions from one_of, nested objects and lists. There is no second definition to keep in sync and no code generator, and your own rules can declare their types too.

user.ts

ts
import LIVR from 'livr';
import type { InferFromSchema } from 'livr/types';

const schema = {
  email: ['required', 'email'],
  role: { one_of: ['admin', 'user'] },
  address: { nested_object: {
    city: ['required', 'string'],
    zip: 'positive_integer',
  }},
  tags: { list_of: 'string' },
} as const;

type User = InferFromSchema<typeof schema>;

const validator = new LIVR.Validator<User>(schema);
const user = validator.validate(input); // User | false

the inferred type of User

ts
{
  email: string;
  role?: 'admin' | 'user';
  address?: {
    city: string;
    zip?: number;
  };
  tags?: string[];
}

How type inference works โ†’ ย ยทย  Try it in the TypeScript Playground โ†—๏ธŽ

Zero dependencies

Nothing to install but LIVR โ€‹

livr has no runtime dependencies, so a compromised or abandoned package somewhere in the tree cannot reach your validation. It works in Node.js, browsers, workers and edge runtimes without polyfills.

0
runtime dependencies
~2 KB
validator core, min+gzip
~5 KB
with all 35 built-in rules, min+gzip. Import only the rules you use to make it smaller

Why zero dependencies โ†’

Proven

Maintained for 13 years, backed by a specification โ€‹

The library has been maintained since 2013 and its version 2 API has been stable since 2016. It implements the LIVR specification, which comes with a shared test suite of JSON files. When an edge case is found in any of the implementations, in any language, it becomes a test case, and every implementation is checked against it. This library passes the whole suite.

How the specification keeps implementations honest โ†’

Everything else you expect โ€‹

  • Error codes shaped like your data: { address: { zip: 'NOT_POSITIVE_INTEGER' } }, easy to translate and to map to form fields.
  • Validation and transformation in one pass: trim, to_lc, default and type coercion of form strings like '42' to 42.
  • Your own vocabulary: combine rules into aliases such as adult_age with their own error codes, or write a rule in a few lines. Custom rules
  • Strict by default: fields without rules are removed from the output, and the input is never changed.
  • Sync and async: import LIVR from 'livr/async' for rules that query a database or an API.

Quick start โ€‹

bash
npm install livr
ts
import LIVR from 'livr';
import type { InferFromSchema } from 'livr/types';

const schema = {
  name: ['required', 'string'],
  email: ['required', 'email'],
  age: 'positive_integer',
  password: ['required', { min_length: 8 }],
  password2: { equal_to_field: 'password' },
} as const;

type User = InferFromSchema<typeof schema>;

const validator = new LIVR.Validator<User>(schema);
const user = validator.validate(input); // User, or false with validator.getErrors()
All validation rules Every rule with its arguments, error codes and examples. Opens livr-spec.org, the site of the LIVR specification.

Read the guide โ†’ ย ยทย  Try type inference in the TypeScript Playground โ†—๏ธŽ