Skip to main content

Zod vs Yup in 2026: Schema Validation Libraries Compared

·PkgPulse Team

TL;DR

Zod for TypeScript projects, especially with tRPC or React Hook Form. Yup for async validation-heavy forms. Zod (~20M weekly downloads) has better TypeScript inference and has overtaken Yup as the community standard. Yup (~12M downloads) has better async validation patterns and a Formik-first API that some teams prefer. For any new TypeScript project, start with Zod.

Key Takeaways

  • Zod: ~20M weekly downloads — Yup: ~12M (npm, March 2026)
  • Zod infers TypeScript types automatically — Yup requires manual type annotation
  • Yup has better async validation — test() methods are async by default
  • Both integrate with React Hook Form — via resolvers
  • Zod has better error handling — structured ZodError with path info

Schema Definition

// Yup — method chaining, JavaScript-first
import * as yup from 'yup';

const signupSchema = yup.object({
  username: yup.string()
    .required('Username is required')
    .min(3, 'Must be at least 3 characters')
    .max(20, 'Must be at most 20 characters')
    .matches(/^[a-zA-Z0-9_]+$/, 'Only letters, numbers, and underscores'),
  email: yup.string()
    .required('Email is required')
    .email('Must be a valid email'),
  password: yup.string()
    .required('Password is required')
    .min(8, 'Must be at least 8 characters'),
  confirmPassword: yup.string()
    .oneOf([yup.ref('password')], 'Passwords must match'),
  age: yup.number().min(13, 'Must be 13 or older').nullable(),
});

// TypeScript type — must be inferred separately
type SignupType = yup.InferType<typeof signupSchema>;
// Works, but less clean than Zod
// Zod — TypeScript-first schema definition
import { z } from 'zod';

const signupSchema = z.object({
  username: z.string()
    .min(3, 'Must be at least 3 characters')
    .max(20, 'Must be at most 20 characters')
    .regex(/^[a-zA-Z0-9_]+$/, 'Only letters, numbers, and underscores'),
  email: z.string().email('Must be a valid email'),
  password: z.string().min(8, 'Must be at least 8 characters'),
  confirmPassword: z.string(),
  age: z.number().min(13, 'Must be 13 or older').nullable().optional(),
}).refine(
  (data) => data.password === data.confirmPassword,
  { message: 'Passwords must match', path: ['confirmPassword'] }
);

type Signup = z.infer<typeof signupSchema>;
// Signup = { username: string; email: string; password: string; ... }
// Automatically inferred — no separate type annotation needed

Async Validation

// Yup — async test() method is straightforward
const usernameSchema = yup.string()
  .required()
  .test('unique', 'Username is already taken', async (value) => {
    if (!value) return true;
    const exists = await checkUsernameAvailability(value);
    return !exists;
  });

// Yup validates async schemas natively:
try {
  await usernameSchema.validate('alice');
} catch (err) {
  // Validation error with message
}
// Zod — async validation via superRefine
const usernameSchema = z.string()
  .min(3)
  .superRefine(async (value, ctx) => {
    const exists = await checkUsernameAvailability(value);
    if (exists) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: 'Username is already taken',
      });
    }
  });

// Must use parseAsync() for async schemas
const result = await usernameSchema.safeParseAsync('alice');
// Note: Zod's async API is less ergonomic than Yup's

React Hook Form Integration

// Both work with React Hook Form via @hookform/resolvers
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { yupResolver } from '@hookform/resolvers/yup';

// Zod
const { register, handleSubmit, formState: { errors } } = useForm({
  resolver: zodResolver(signupSchema),
});

// Yup
const { register, handleSubmit, formState: { errors } } = useForm({
  resolver: yupResolver(signupSchema),
});

// Both produce the same ergonomics in the component — equal here

Formik + Yup (Legacy Stack)

// Yup was built alongside Formik — deep integration
import { Formik, Form, Field } from 'formik';

const LoginForm = () => (
  <Formik
    initialValues={{ email: '', password: '' }}
    validationSchema={loginSchema} // Yup schemanative Formik support
    onSubmit={(values) => login(values)}
  >
    {({ errors, touched }) => (
      <Form>
        <Field name="email" type="email" />
        {errors.email && touched.email && <div>{errors.email}</div>}
        <Field name="password" type="password" />
        <button type="submit">Login</button>
      </Form>
    )}
  </Formik>
);

If your stack includes Formik, Yup's native integration is still an advantage. But React Hook Form + Zod has become the more popular combination.


Error Messages

// Zod — structured errors with paths
const result = signupSchema.safeParse({ email: 'invalid', password: '123' });
if (!result.success) {
  // Access specific field errors
  const emailError = result.error.issues.find(i => i.path[0] === 'email');
  const allErrors = result.error.flatten().fieldErrors;
  // { email: ['Must be a valid email'], password: ['Must be at least 8 characters'] }
}
// Yup — ValidationError with path
try {
  await signupSchema.validate(data, { abortEarly: false }); // Collect all errors
} catch (err) {
  if (err instanceof yup.ValidationError) {
    const errors = err.inner.reduce((acc, e) => ({
      ...acc,
      [e.path]: e.message,
    }), {});
    // { email: 'Must be a valid email', password: 'Must be at least 8 characters' }
  }
}

When to Choose

Choose Zod when:

  • TypeScript project (automatic type inference is a major DX win)
  • Using tRPC or libraries with native Zod support
  • Using React Hook Form (both work, but Zod is more popular today)
  • New project without existing validation library commitment

Choose Yup when:

  • Using Formik (native integration, less setup)
  • Async validation is complex and central to your forms
  • Existing codebase with Yup already integrated
  • JavaScript (not TypeScript) project where Zod's DX advantage is smaller

Compare Zod and Yup package health on PkgPulse.

See the live comparison

View zod vs. yup on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.