As JavaScript apps get more complex, checking that your data is correct has become more important than ever. Whether you're building the frontend or backend, you need a solid way to make sure your data is valid. This helps improve the user experience and keeps your app secure. Two popular libraries that help with this are Yup and Zod.
Yup has been around for a while and is well-known in React, especially if you use form libraries like Formik. Jason Quense created Yup based on ideas from Joi, but made it work better in the browser and with React. It uses a chainable style and offers lots of built-in validation features, which is why many React developers rely on it.
Zod is newer. Colin McDonnell built it in 2020 with TypeScript in mind. Zod focuses on filling the gap between runtime checks and TypeScript's static type system. Even though it’s made for TypeScript, it also works great in regular JavaScript projects.
In this guide, you’ll see how Yup and Zod handle validation differently. By the end, you’ll better understand which fits your project, your coding style, and your technical needs.
What is Yup?
Yup started in React and quickly became popular because it works well with Formik, a widely-used form library. This connection helped Yup grow fast among React developers who wanted an easy way to handle validation.
Yup stands out because of its simple, chainable API. You build validation rules by chaining methods, which makes your code easy to read and understand. It feels natural for anyone who is used to writing JavaScript.
It also supports synchronous and asynchronous checks, custom error messages, conditional logic, and type coercion. It also lets you set up different languages through custom locales, which is helpful if your app serves users worldwide.
What is Zod?
Zod is a newer library initially built with TypeScript in mind. While other tools added TypeScript support later, Zod was designed to connect runtime validation with TypeScript’s type system right out of the box.
Zod is special because it can act as a single source of truth. You define your schema once, which works for validation and type checking. You don’t have to write and update two separate things — your types and your validation logic stay in sync.
Zod focuses on type safety, flexibility, and a smooth developer experience. It gives clear, structured error messages that are easy to use in your UI.
Yup vs. Zod: a quick comparison
Choosing between Yup and Zod depends on your project needs and how your team likes to work. Here’s a simple side-by-side look at how they compare in key areas:
Feature | Yup | Zod |
---|---|---|
Design philosophy | JavaScript-first with TypeScript support | TypeScript-first with JavaScript compatibility |
Primary ecosystem | React, especially with Formik | Modern TypeScript projects, tRPC, Next.js |
Schema definition | Chainable, fluent API with descriptive methods | Concise builder pattern with TypeScript integration |
Error handling | Rich, customizable messaging with i18n support | Structured error objects with path information |
TypeScript integration | Added via type definitions and inference utilities | Native, built-in type inference from schemas |
Parsing capabilities | Basic transformation with limited coercion | Advanced parsing with comprehensive transformations |
Learning curve | Gentle for JavaScript developers | Steeper if you're new to TypeScript |
Bundle size | ~24KB minified + gzipped | ~8KB minified + gzipped |
Performance | Good with optimization options | Excellent with minimal overhead |
Schema composition | Supported with merge and concat | First-class with composition operators |
Popular integrations | Formik, React Hook Form, MUI | tRPC, React Hook Form, Next.js, Remix |
Schema definition
The approach to defining validation schemas fundamentally shapes how developers interact with a validation library. Yup and Zod offer distinctly different experiences that reflect their origins and design philosophies.
Yup uses a highly expressive, method-chaining API that emphasizes readability. This fluent interface allows developers to build validation rules that almost read like English sentences:
import * as yup from 'yup';
const productSchema = yup.object({
name: yup.string()
.required('Product name is required')
.min(3, 'Name must be at least 3 characters'),
price: yup.number()
.required('Price is required')
.positive('Price must be positive'),
// Additional fields...
});
Yup's strength lies in its expressiveness and the ability to customize error messages inline. This is especially useful in apps where users need clear, helpful feedback. Yup also gives you fine control over validation behavior with options like strict
mode, abortEarly
, and stripUnknown
.
Zod takes a more concise, object-oriented approach to schema definition. Its API emphasizes composability and type integration:
import { z } from 'zod';
const productSchema = z.object({
name: z.string()
.min(3, 'Name must be at least 3 characters'),
price: z.number()
.positive('Price must be positive'),
// Additional fields...
});
type Product = z.infer<typeof productSchema>;
Zod's philosophy focuses on conciseness and static typing. Zod schemas are usually shorter than Yup ones and often require less code for the same validation logic. A key feature is z.infer<typeof schema>
, which lets you generate TypeScript types directly from your schema—so you don’t have to write types separately.
Both libraries allow you to compose and reuse validation rules, but they approach this differently. Yup provides .concat()
and .shape()
for combining schemas, while Zod offers more functional composition patterns like .extend()
, .merge()
, and .pick()/.omit()
.
Validation patterns
How validation executes at runtime significantly impacts application architecture and error handling. Yup and Zod implement different patterns that reflect their underlying design philosophies.
Yup offers multiple validation methods to suit different scenarios. For synchronous validation, you can use .validate()
with a callback or .validateSync()
for immediate results:
// Synchronous validation with options
try {
const validData = addressSchema.validateSync(formData, {
abortEarly: false, // Return all errors, not just the first one
stripUnknown: true // Remove fields not defined in the schema
});
saveAddress(validData);
} catch (error) {
displayErrors(error.errors);
}
Yup also provides more granular control with methods like .isValid()
for simple boolean checks and .cast()
for type coercion without full validation. This flexibility is especially useful in form libraries like Formik, where different validation behaviors might be needed at different stages of the form lifecycle.
Zod follows a "parse or throw" pattern that aligns with TypeScript's type checking philosophy. The primary methods are .parse()
for synchronous validation and .parseAsync()
for asynchronous validation:
// Non-throwing alternative with safeParse
const result = addressSchema.safeParse(formData);
if (result.success) {
saveAddress(result.data);
} else {
displayErrors(result.error.format());
}
For cases where you prefer not to use exceptions for control flow, Zod provides .safeParse()
and .safeParseAsync()
, which return a discriminated union of success or error results. This approach works especially well with TypeScript's control flow analysis.
Handling complex validation scenarios
Real-world applications often require validation that goes beyond simple field-level checks. Business rules, interdependent fields, and conditional validation are common challenges that test the capabilities of validation libraries.
Yup addresses complex validation through a combination of built-in methods and custom tests. Its .when()
method is particularly useful for conditional validation:
const phoneField = yup.string()
.when('method', {
is: 'international',
then: schema => schema.required('Phone required for international shipping')
.matches(/^\+[1-9]\d{1,14}$/, 'Please enter phone with country code'),
otherwise: schema => schema.optional()
});
For validation that involves multiple fields, Yup provides the context object in custom tests:
const confirmPassword = yup.string()
.required('Please confirm your password')
.test(
'passwords-match',
'Passwords must match',
function(value) {
return this.parent.newPassword === value;
}
);
Zod approaches complex validation through its refinement system. For conditional validation, you can use .superRefine()
which gives access to the entire object being validated:
const shippingSchema = z.object({
// Fields definition...
}).superRefine((data, ctx) => {
// Conditional validation for international shipping
if (data.method === 'international' && (!data.phone || data.phone.length === 0)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Phone required for international shipping',
path: ['phone']
});
}
});
For validation involving related fields, Zod provides .refine()
for adding custom validation rules:
const passwordSchema = z.object({
password: z.string().min(8),
confirm: z.string()
}).refine(
data => data.password === data.confirm,
{
message: 'Passwords must match',
path: ['confirm']
}
);
Both libraries can handle complex validation scenarios, but they favor different approaches. Yup's .when()
method and context-aware tests make conditional validation more declarative and often easier to read. Zod's refinement system is more flexible and powerful, particularly for complex interdependencies, but can require more code for simple conditions.
Type safety and integration
TypeScript has become increasingly important in modern JavaScript development. The way a validation library integrates with TypeScript can significantly impact developer productivity and code quality.
Yup was initially developed for JavaScript and later added TypeScript support. It provides comprehensive type definitions, but there's an inherent separation between validation schemas and TypeScript types:
// Define the TypeScript interface separately
interface User {
id: number;
name: string;
email: string;
// Additional fields...
}
// Use both schema and type in a function
function processUser(input: unknown): User {
// Validate with schema
const validData = userSchema.validateSync(input);
// Type assertion needed to connect validation result to TypeScript type
return validData as User;
}
Yup does offer a type inference utility, but it has limitations with complex schemas and often doesn't capture all the nuances of runtime validation.
Zod was designed from the ground up to bridge this gap. It provides a single source of truth for both validation and types:
// Derive the type directly from the schema
type User = z.infer<typeof userSchema>;
// Use in a function with perfect type alignment
function processUser(input: unknown): User {
// Validation and type narrowing happen together
return userSchema.parse(input);
}
With Zod, there's no possibility of mismatch between validation rules and TypeScript types. When you update validation logic, the corresponding types update automatically. This tight coupling eliminates an entire category of potential bugs.
Ecosystem and community
A validation library's ecosystem—framework integrations, tooling, documentation, and community support—can significantly impact development experience. Yup and Zod have built different ecosystems that reflect their origins and target audiences.
Yup has deep roots in the React ecosystem, particularly through its integration with Formik. This pairing has been a standard solution for form handling in React applications for years:
<Formik
initialValues={{ firstName: '', lastName: '', email: '' }}
validationSchema={validationSchema}
onSubmit={values => console.log(values)}
>
{/* Form fields */}
</Formik>
Yup also integrates well with other form libraries like React Hook Form, and has established patterns for use with Redux Form, Final Form, and vanilla React. Its long-standing presence in the ecosystem means there's extensive community knowledge, Stack Overflow answers, and third-party extensions available.
Zod has built its ecosystem around TypeScript-first frameworks and libraries. It has particularly strong integration with tRPC, a framework for building end-to-end typesafe APIs:
const appRouter = t.router({
createUser: t.procedure
.input(createUserSchema)
.mutation(async ({ input }) => {
// Input is validated and typed
const user = await db.users.create({ data: input });
return user;
})
});
Zod also integrates seamlessly with React Hook Form via the @hookform/resolvers
package:
const { register, handleSubmit, errors } = useForm({
resolver: zodResolver(formSchema)
});
Zod has gained significant traction in the Next.js and Remix communities, where end-to-end type safety is highly valued. It works well with modern approaches like React Server Components and is often paired with Prisma for type-safe database access.
The ecosystem choice often aligns with your broader technology decisions. If you're working in the React ecosystem with Formik or have an existing codebase using Yup, continuing with Yup offers continuity and familiarity. For projects leveraging modern TypeScript patterns, particularly with Next.js, tRPC, or Remix, Zod provides a more integrated experience.
Final thoughts
Yup and Zod are both solid choices, but they shine in different ways. Yup is a good fit for JavaScript projects, especially if you're working with forms and want a simple, readable API.
Zod, however, offers a modern, TypeScript-first approach. It keeps validation and types in sync, which reduces bugs and makes your code easier to maintain. If you're using TypeScript—or planning to—Zod is the better long-term choice.
For most modern web apps, especially those built with frameworks like Next.js or tRPC, Zod is the way to go.
Make your mark
Join the writer's program
Are you a developer and love writing and sharing your knowledge with the world? Join our guest writing program and get paid for writing amazing technical guides. We'll get them to the right readers that will appreciate them.
Write for us
Build on top of Better Stack
Write a script, app or project on top of Better Stack and share it with the world. Make a public repository and share it with us at our email.
community@betterstack.comor submit a pull request and help us build better products for everyone.
See the full list of amazing projects on github