Rules are just data
A schema is a plain JSON structure, not code. Store it in a database, send it over the wire, share it with services in other languages, and load it safely even from untrusted sources.
Why data beats code
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.
{
email: ['required', 'email'],
age: ['required', 'positive_integer'],
role: { one_of: ['admin', 'user'] }
}{
email: 'ann@example.com',
age: '42',
role: 'admin',
isAdmin: true
}{
email: 'ann@example.com',
age: 42,
role: 'admin'
}InferFromSchema<typeof schema>{ email: string; age: number; role?: 'admin' | 'user' }Rules are just data
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
{
"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
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.
Incredible performance
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.
Node.js 26.8.2, AMD Ryzen 9 5950X, 2026-10-18. Absolute numbers depend on the hardware, compare the ratios.
Type inference
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
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 | falsethe inferred type of User
{
email: string;
role?: 'admin' | 'user';
address?: {
city: string;
zip?: number;
};
tags?: string[];
}How type inference works โ ย ยทย Try it in the TypeScript Playground โ๏ธ
Zero dependencies
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.
Proven
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.
{ address: { zip: 'NOT_POSITIVE_INTEGER' } }, easy to translate and to map to form fields.trim, to_lc, default and type coercion of form strings like '42' to 42.adult_age with their own error codes, or write a rule in a few lines. Custom rulesimport LIVR from 'livr/async' for rules that query a database or an API.npm install livrimport 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()Read the guide โ ย ยทย Try type inference in the TypeScript Playground โ๏ธ