Very good for internal tools, low-code platforms, or apps that require typed validation
⚖️ Zod vs. Valibot
Nearly similar in ops/sec, suggesting similar architecture or design trade-offs.
Good for TypeScript-first developer experience but noticeably slower than Ajv/xvalidators.
Both offer great DX.
Valibot is focused on bundle size + performance, good for frontend.
Zod still preferred if you need ecosystem maturity and developer ergonomics.
❌ Joi and Yup
Significantly slower, not suited for performance-sensitive applications.
Useful in some backend contexts (Joi) or form validation (Yup), but not recommended when performance matters.
🏁 Recommendation by Use Case
Use Case
Library
Reason
High-throughput backend APIs
Ajv
Fastest JSON Schema validator
Minimal frontend apps with type-safe apps and custom rules
xvalidators
Good speed, excellent TypeScript integration
Minimal frontend apps
Valibot
Lightweight and efficient
DX-focused development
Zod
Clean syntax, popular, ideal for prototypes
Legacy/enterprise backend apps
Joi
Powerful features, but low performance
Form validation in React (Formik)
Yup
Integrated with form tools, but slow
📌 Summary
If you need speed: → Ajv
If you want typescript + good speed + small: → xvalidators
If you prefer a big community support over speed: → Zod or Valibot
If you already use Formik or legacy stack: → Yup or Joi
📊 Feature Comparison
Feature / Library
Ajv
xvalidators
Zod
Valibot
Joi
Yup
Required
✅
✅
✅
✅
✅
✅
Object / Nested validation
✅
✅
✅
✅
✅
✅
Array validation
✅
✅
✅
✅
✅
✅
Email
✅ format: "email"
✅ format: "email"
✅ .email()
✅ string([email()])
✅ .email()
✅ .email()
URL
✅ format: "uri"
✅ format: "url"
✅ .url()
✅ string([url()])
✅ .uri()
✅ .url()
IPv4 check
✅ format: "ipv4"
✅ format: "ipv4"
⚠️ Regex only
⚠️ Regex only
✅ .ip({ version: ["ipv4"] })
⚠️ Regex only
IPv6 check
✅ format: "ipv6"
✅ format: "ipv6"
⚠️ Regex only
⚠️ Regex only
✅ .ip({ version: ["ipv6"] })
⚠️ Regex only
Phone number
⚠️ No built-in — regex only
✅ format: "phone"
⚠️ No built-in — regex only
⚠️ No built-in — regex only
⚠️ No built-in — use .pattern() or .custom() with phone regex
⚠️ No built-in — use .matches() with regex
Fax number
⚠️ No built-in — regex only
✅ format: "fax"
⚠️ No built-in — regex only
⚠️ No built-in — regex only
⚠️ No built-in — use .pattern() or .custom() with phone regex
⚠️ No built-in — use .matches() with regex
Regex match
✅ pattern
✅ pattern
✅ .regex()
✅ string([regex(...)])
✅ .pattern()
✅ .matches()
Enum values
✅ enum
✅ enum
✅ z.enum([...])
✅ enumType()
✅ .valid(a,b,c)
✅ .oneOf([...])
Min/Max length (string)
✅
✅
✅
✅
✅
✅
Number type
✅
✅
✅
✅
✅
✅
Min/Max value (number/date)
✅
✅
✅
✅
✅
✅
Precision (decimal places)
⚠️multipleOf
✅ precision: n
⚠️.multipleOf() / .refine()
⚠️.refine()
✅ .precision(n)
⚠️.test()
Scale (integer + fraction limits)
⚠️ Custom
✅ scale: n
⚠️ Custom
⚠️ Custom
⚠️ Partial via .precision() + .max()
⚠️ Custom
Date type
✅ format: "date" / "date-time"
✅ type: "date" | "datetime"
✅ z.date() / .datetime()
✅ date()
✅ .date()
✅ .date()
Relative date (now/today/tomorrow/yesterday)
⚠️ Custom keyword
✅ min: "now" | "today" | "tomorrow" | "yesterday"
⚠️.refine()
⚠️.refine()
✅ .min('now') / .max('now') + Date values
⚠️ Custom .min() / .max()
Performance
⚡ Fastest
🔥 Fast
🐇 Good
🐇 Good
🐢 Slower
🐢 Slower
Bundle size (browser)
Large (~60KB+, tree-shakeable)
Small (~4KB)
Medium (~20KB)
Small (~6–8KB)
Large (~70KB+)
Medium (~30KB)
Best use case
API validation, JSON Schema compliance
API validation, JSON Schema compliance, minimal frontend
TS-safe runtime validation
Lightweight TS-safe validation
Rich backend business rules
Frontend form validation
🧱 Samples
Sample 1
import{Attributes,StringMap,validate}from"xvalidators"interfaceResources{[key: string]: StringMap}constenResource: StringMap={error_undefined: "{0} is not allowed to exist.",error_exp: "{0} does not match the regular expression.",error_type: "Invalid datatype. Type of {0} cannot be {1}.",error_required: "{0} is required.",error_minlength: "{0} cannot be less than {1} characters.",error_maxlength: "{0} cannot be greater than {1} characters.",error_email: "{0} is not a valid email address.",error_integer: "{0} is not a valid integer.",error_number: "{0} is not a valid number.",error_precision: "{0} has a valid precision. Precision must be less than or equal to {1}",error_scale: "{0} has a valid scale. Scale must be less than or equal to {1}",error_phone: "{0} is not a valid phone number.",error_fax: "{0} is not a valid fax number.",error_url: "{0} is not a valid URL.",error_ipv4: "{0} is not a valid ipv4.",error_ipv6: "{0} is not a valid ipv6.",error_min: "{0} must be greater than or equal to {1}.",error_max: "{0} must be less than or equal to {1}.",error_date: "{0} is not a valid date.",error_enum: "{0} must be one of {1}.",username: "Username",date_of_birth: "Date Of Birth",telephone: "Telephone",email: "Email",website: "Website",status: "User Status",credit_limit: "Credit Limit",}constviResource={error_undefined: "{0} không được phép tồn tại.",error_exp: "{0} không khớp với biểu thức chính quy.",error_type: "Kiểu dữ liệu không hợp lệ. Kiểu của {0} không thể là {1}.",error_required: "{0} là bắt buộc.",error_minlength: "{0} không được ít hơn {1} ký tự.",error_maxlength: "{0} không được nhiều hơn {1} ký tự.",error_email: "{0} không phải là địa chỉ email hợp lệ.",error_integer: "{0} không phải là số nguyên hợp lệ.",error_number: "{0} không phải là số hợp lệ.",error_precision: "{0} có độ chính xác không hợp lệ. Độ chính xác phải nhỏ hơn hoặc bằng {1}.",error_scale: "{0} có thang đo không hợp lệ. Thang đo phải nhỏ hơn hoặc bằng {1}.",error_phone: "{0} không phải là số điện thoại hợp lệ.",error_fax: "{0} không phải là số fax hợp lệ.",error_url: "{0} không phải là URL hợp lệ.",error_ipv4: "{0} không phải là địa chỉ IPv4 hợp lệ.",error_ipv6: "{0} không phải là địa chỉ IPv6 hợp lệ.",error_min: "{0} phải lớn hơn hoặc bằng {1}.",error_max: "{0} phải nhỏ hơn hoặc bằng {1}.",error_date: "{0} không phải là ngày hợp lệ.",error_enum: "{0} phải là một trong các giá trị sau: {1}.",username: "Tên người dùng",date_of_birth: "Ngày sinh",telephone: "Điện thoại",email: "Địa chỉ email",website: "Trang web",status: "Trạng thái người dùng",credit_limit: "Hạn mức tín dụng",}constresources: Resources={en: enResource,vi: viResource,}functiongetResource(lang: string): StringMap{returnresources[lang]||resources["en"]}constresource=getResource("en")// or "vi" for VietnameseinterfaceUser{id: stringusername: stringemail?: stringphone?: stringip?: stringdateOfBirth?: Datewebsite?: stringcreditLimit?: numberstatus?: string[]}constuserSchema: Attributes={id: {length: 40,},username: {required: true,length: 255,resource: "username",},email: {format: "email",required: true,length: 120,resource: "email",},phone: {format: "phone",required: true,length: 14,resource: "telephone",},ip: {format: "ipv4",length: 15,},website: {length: 255,format: "url",resource: "website",},dateOfBirth: {type: "datetime",},creditLimit: {type: "number",scale: 2,min: 1,max: 200000,resource: "credit_limit",},status: {type: "strings",enum: ["active","inactive","online","offline","away"],resource: "status",},}constinvalidUser={id: "12345678901234567890123456789012345678901",// 41 characters => maximum 40 => invalid// username: "james.howlett", // required => invalidemail: "james.howlett@gmail",// invalid emailphone: "abcd1234",// required => invalidip: "abcd1234",// invalid => not requiredwebsite: "invalid website",dateOfBirth: "1974-03-25",// valid date => the library will convert to datecreditLimit: 10000000.255,// invalid precision and scale => precision must be less than or equal to 10 digitsstatus: ["active","busy"],age: 50,// does not exist in schema => invalid}leterrors=validate(invalidUser,userSchema,resource)console.log("Validate James Howlett: ",errors)constuser: User={id: "1234567890123456789012345678901234567890",username: "tony.stark",email: "tony.stark@gmail.com",phone: "+1234567890",website: "https://github.com/core-ts",dateOfBirth: newDate("1963-03-25"),creditLimit: 100000.25,status: ["active","online"],}errors=validate(user,userSchema,resource,true)console.log("Validate Tony Stark (no error): ",errors)// should be empty
Out put is:
Validate James Howlett: [
{
field: 'id',
code: 'maxlength',
message: 'id cannot be greater than 40 characters.',
param: 40
},
{
field: 'email',
code: 'email',
message: 'Email is not a valid email address.'
},
{
field: 'phone',
code: 'phone',
message: 'Telephone is not a valid phone number.'
},
{ field: 'ip', code: 'ipv4', message: 'ip is not a valid ipv4.' },
{
field: 'website',
code: 'url',
message: 'Website is not a valid URL.'
},
{
field: 'creditLimit',
code: 'scale',
message: 'Credit Limit has a valid scale. Scale must be less than or equal to 2'
},
{
field: 'creditLimit',
code: 'max',
message: 'Credit Limit must be less than or equal to 200000.',
param: 200000
},
{
field: 'status',
code: 'enum',
message: 'User Status must be one of active, inactive, online, offline, away.',
param: 'active, inactive, online, offline, away'
},
{
field: 'age',
code: 'undefined',
message: 'age is not allowed to exist.'
},
{
field: 'username',
code: 'required',
message: 'Username is required.'
}
]
Validate Tony Stark (no error): []
Sample 2
import{Attributes,StringMap,validate}from"xvalidators"interfaceResources{[key: string]: StringMap}constenResource: StringMap={error_undefined: "{0} is not allowed to exist.",error_exp: "{0} does not match the regular expression.",error_type: "Invalid datatype. Type of {0} cannot be {1}.",error_boolean: "{0} cannot be boolean.",error_strings: "{0} must be an string array.",error_numbers: "{0} must be an number array.",error_integers: "{0} must be an number array.",error_datetimes: "{0} must be an date time array.",error_dates: "{0} must be an date array.",error_required: "{0} is required.",error_minlength: "{0} cannot be less than {1} characters.",error_maxlength: "{0} cannot be greater than {1} characters.",error_array_min: "Length of {0} cannot be less than {1}.",error_array_max: "Length of {0} cannot be greater than {1}.",error_email: "{0} is not a valid email address.",error_integer: "{0} is not a valid integer.",error_number: "{0} is not a valid number.",error_precision: "{0} has a valid precision. Precision must be less than or equal to {1}",error_scale: "{0} has a valid scale. Scale must be less than or equal to {1}",error_phone: "{0} is not a valid phone number.",error_fax: "{0} is not a valid fax number.",error_url: "{0} is not a valid URL.",error_ipv4: "{0} is not a valid ipv4.",error_ipv6: "{0} is not a valid ipv6.",error_min: "{0} must be greater than or equal to {1}.",error_max: "{0} must be less than or equal to {1}.",error_gt: "{0} must be greater than {1}.",error_lt: "{0} must be less than {1}.",error_date: "{0} is not a valid date.",error_enum: "{0} must be one of {1}.",date_of_birth: "Date Of Birth",telephone: "Telephone",email: "Email",website: "Website",status: "User Status",state: "State",zip: "Zip Code",zip_code: "Zip code is not valid.",quality: "Quality",level: "Level",}constviResource={error_undefined: "{0} không được phép tồn tại.",error_exp: "{0} không khớp với biểu thức chính quy.",error_type: "Kiểu dữ liệu không hợp lệ. Kiểu của {0} không thể là {1}.",error_boolean: "{0} không thể là kiểu boolean.",error_strings: "{0} phải là một mảng chuỗi.",error_numbers: "{0} phải là một mảng số.",error_integers: "{0} phải là một mảng số nguyên.",error_datetimes: "{0} phải là một mảng ngày giờ.",error_dates: "{0} phải là một mảng ngày.",error_required: "{0} là bắt buộc.",error_minlength: "{0} không được ít hơn {1} ký tự.",error_maxlength: "{0} không được nhiều hơn {1} ký tự.",error_array_min: "Độ dài của {0} không được nhỏ hơn {1}.",error_array_max: "Độ dài của {0} không được lớn hơn {1}.",error_email: "{0} không phải là địa chỉ email hợp lệ.",error_integer: "{0} không phải là số nguyên hợp lệ.",error_number: "{0} không phải là số hợp lệ.",error_precision: "{0} có độ chính xác không hợp lệ. Độ chính xác phải nhỏ hơn hoặc bằng {1}.",error_scale: "{0} có thang đo không hợp lệ. Thang đo phải nhỏ hơn hoặc bằng {1}.",error_phone: "{0} không phải là số điện thoại hợp lệ.",error_fax: "{0} không phải là số fax hợp lệ.",error_url: "{0} không phải là URL hợp lệ.",error_ipv4: "{0} không phải là địa chỉ IPv4 hợp lệ.",error_ipv6: "{0} không phải là địa chỉ IPv6 hợp lệ.",error_min: "{0} phải lớn hơn hoặc bằng {1}.",error_max: "{0} phải nhỏ hơn hoặc bằng {1}.",error_gt: "{0} phải lớn hơn {1}.",error_lt: "{0} phải nhỏ hơn {1}.",error_date: "{0} không phải là ngày hợp lệ.",error_enum: "{0} phải là một trong các giá trị sau: {1}.",date_of_birth: "Ngày sinh",telephone: "Điện thoại",email: "Địa chỉ email",website: "Trang web",status: "Trạng thái người dùng",state: "Tiểu bang",zip: "Mã bưu điện",zip_code: "Mã bưu điện không hợp lệ.",quality: "Chất lượng",level: "Cấp độ",}constresources: Resources={en: enResource,vi: viResource,}functiongetResource(lang: string): StringMap{returnresources[lang]||resources["en"]}constresource=getResource("en")interfaceSkill{skill: stringlevel: number}interfaceAchievement{subject: stringdescription: stringquality: stringskills?: Skill[]}interfaceAddress{street: stringcity: stringstate: stringzip: string}interfaceUser{id: stringusername: stringemail?: stringphone?: stringdateOfBirth?: Datewebsite?: stringcreditLimit?: numberstatus?: string[]address?: Addressachievements?: Achievement[]}constskillSchema: Attributes={skill: {required: true,length: 15,},level: {required: true,type: "integer",enum: [1,2,3,4,5],resource: "level",},}constachievementSchema: Attributes={subject: {required: true,length: 255,},description: {required: true,length: 255,},quality: {required: true,length: 255,enum: ["Excellent","Good","Average","Poor","Very Poor"],resource: "quality",},skills: {type: "array",typeof: skillSchema,},}constaddressSchema: Attributes={street: {required: true,length: 255,},city: {required: true,length: 255,},state: {length: 2,exp: /^[A-Z]{2}$/,// resource: "State is not valid.",},zip: {exp: /(^\d{5}$)|(^\d{5}-\d{4}$)/,resource: "zip_code",},}constuserSchema: Attributes={id: {length: 40,},username: {required: true,length: 255,},email: {format: "email",required: true,length: 120,resource: "email",},phone: {format: "phone",required: true,length: 14,resource: "telephone",},website: {length: 255,format: "url",resource: "website",},dateOfBirth: {type: "datetime",},creditLimit: {type: "number",precision: 10,scale: 2,min: 1,},status: {type: "strings",enum: ["active","inactive","online","offline","away"],resource: "status",},address: {type: "object",typeof: addressSchema,},achievements: {type: "array",typeof: achievementSchema,},}leterrors=validate({username: "james.howlett",email: "james.howlett@gmail",// invalid emailphone: "",// required => invalidwebsite: "https://james.howlett.com",dateOfBirth: "1974-03-25",// valid date => the library will convert to dateage: 50,// does not exist in schema => invalid},userSchema,resource,)console.log("Validate James Howlett: ",errors)constuser: User={id: "1234567890123456789012345678901234567890",username: "tony.stark",email: "tony.stark@gmail.com",phone: "+1234567890",website: "https://github.com/core-ts",dateOfBirth: newDate("1963-03-25"),creditLimit: 100000.25,status: ["active","online"],address: {street: "123 Stark Tower",city: "New York",state: "NY",zip: "07008",},achievements: [{subject: "Avengers",description: "Leader of the Avengers team",quality: "Excellent",skills: [{skill: "Leadership",level: 5,},{skill: "Technology",level: 4,},{skill: "Martial Arts",level: 3,},],},{subject: "Iron Suite",description: "Iron Armor Suit",quality: "Excellent",},],}errors=validate(user,userSchema,resource,true)console.log("Validate Tony Stark (no error): ",errors)// should be emptyconstinvalidUser: User={id: "12345678901234567890123456789012345678901",// 41 characters => maximum 40 => invalidusername: "peter.parker",email: "test",// invalid emailphone: "abcd1234",// invalid phone numberwebsite: "wrong url",// invalid URLdateOfBirth: newDate("1962-08-25"),creditLimit: 10000000.255,// invalid precision and scale => precision must be less than or equal to 10 digitsstatus: ["active","busy"],address: {street: "123 Stark Tower",city: "New York",state: "New York",zip: "999999",// invalid zip code, does not match regex},achievements: [{subject: "Avengers",description: "Member of the Avengers team",quality: "Normal",// invalid quality, must be one of ["Excellent", "Good", "Average", "Poor", "Very Poor"]skills: [{skill: "Technology",level: 5,},{skill: "Martial Arts",level: 6,// invalid level, must be be one of [1, 2, 3, 4, 5]},],},],}errors=validate(invalidUser,userSchema,resource,true)console.log("Validate Peter Parker: ",errors)
Out put is:
Validate James Howlett: [
{
field: 'email',
code: 'email',
message: 'Email is not a valid email address.'
},
{
field: 'phone',
code: 'required',
message: 'Telephone is required.'
},
{
field: 'age',
code: 'undefined',
message: 'age is not allowed to exist.'
}
]
Validate Tony Stark (no error): []
Validate Peter Parker: [
{
field: 'id',
code: 'maxlength',
message: 'id cannot be greater than 40 characters.',
param: 40
},
{
field: 'email',
code: 'email',
message: 'Email is not a valid email address.'
},
{
field: 'phone',
code: 'phone',
message: 'Telephone is not a valid phone number.'
},
{
field: 'website',
code: 'url',
message: 'Website is not a valid URL.'
},
{
field: 'creditLimit',
code: 'precision',
message: 'creditLimit has a valid precision. Precision must be less than or equal to 10'
},
{
field: 'status',
code: 'enum',
message: 'User Status must be one of active, inactive, online, offline, away.',
param: 'active, inactive, online, offline, away'
},
{
field: 'address.state',
code: 'maxlength',
message: 'state cannot be greater than 2 characters.',
param: 2
},
{
field: 'address.state',
code: 'exp',
message: 'state does not match the regular expression.'
},
{ field: 'address.zip', code: 'exp', message: 'zip_code' },
{
field: 'achievements[0].quality',
code: 'enum',
message: 'Quality must be one of Excellent, Good, Average, Poor, Very Poor.',
param: 'Excellent, Good, Average, Poor, Very Poor'
},
{
field: 'achievements[0].skills[1].level',
code: 'enum',
message: 'Level must be one of 1, 2, 3, 4, 5.',
param: '1, 2, 3, 4, 5'
}
]