Even though my API resembles other solutions at first glance, the implementation and structure of the source code is very different. In the following, I would like to highlight the differences that can be beneficial for both you and your users.

Modular design

Instead of relying on a few large functions with many methods, my API design and source code is based on many small and independent functions, each with just a single task. This modular design has several advantages.

On one hand, my functionality can be easily extended with external code. On the other, it makes my source code more robust and secure because the functionality of the individual functions as well as special edge cases can be tested much easier through unit tests.

However, perhaps the biggest advantage is that a bundler can use the import statements to remove any code that is not needed. Thus, only the code that is actually used ends up in the production build. This allows me to extend my functionality with additional functions without increasing the bundle size for all users.

This can make a big difference, especially for client-side validation, as it reduces the bundle size and, depending on the framework, speeds up the startup time.

import { email, minLength, object, string } from 'valibot'; // 1.4 kB

const LoginSchema = object({
  email: string([
    minLength(1, 'Please enter your email.'),
    email('The email address is badly formatted.'),
  password: string([
    minLength(1, 'Please enter your password.'),
    minLength(8, 'Your password must have 8 characters or more.'),

Comparison with Zod

For example, to validate a simple login form, Zod requires 11.8 kB whereas I require only 1.4 kB. That's a 88 % reduction in bundle size. This is due to the fact that Zod's functions have several methods with additional functionalities, that cannot be easily removed by current bundlers when they are not executed in your source code.

import { object, string } from 'zod'; // 11.8 kB

const LoginSchema = object({
  email: string()
    .min(1, 'Please enter your email.')
    .email('The email address is badly formatted.'),
  password: string()
    .min(1, 'Please enter your password.')
    .min(8, 'Your password must have 8 characters or more.'),


With a schema library, a distinction must be made between startup performance and runtime performance. Startup performance describes the time required to load and initialize the library. This benchmark is mainly influenced by the bundle size and the amount of work required to create a schema. Runtime performance describes the time required to validate unknown data using a schema.

Since my implementation is optimized to minimize the bundle size and the effort of initialization, there is hardly a library that performs better in a TTI benchmark than I do. In terms of runtime performance, I am in the midfield. Roughly speaking, I am about twice as fast as Zod, but much slower than Typia and TypeBox, because I don't yet use a compiler that can generate optimal code, and my implementation doesn't allow the use of the Function constructor.

Further details on performance can be found in the bachelor's thesis I am based on.


Thanks to all the contributors who helped make this page better!

  • GitHub profile picture of fabian-hiller
  • GitHub profile picture of nikolailehbrink
  • GitHub profile picture of Neon-20


Thanks to our partners who support the project ideally and financially.


Thanks to our GitHub sponsors who support the project financially.

  • GitHub profile picture of dailydotdev
  • GitHub profile picture of ivan-mihalic
  • GitHub profile picture of KATT
  • GitHub profile picture of osdiab
  • GitHub profile picture of Thanaen
  • GitHub profile picture of ruiaraujo012
  • GitHub profile picture of hyunbinseo
  • GitHub profile picture of caegdeveloper