3.22.4 • Published 7 months ago

zod v3.22.4

Weekly downloads
49,909
License
MIT
Repository
github
Last release
7 months ago

Table of contents

Installation

To install the latest version:

npm install --save zod
yarn add zod

TypeScript versions

Zod 1.0.x is compatible with TypeScript 3.2+.

You must use strict mode for Zod to correctly infer the types of your schemas! Add "strict": true inside "compilerOptions" in your tsconfig.json.

Usage

Zod is a validation library designed for optimal developer experience. It's a TypeScript-first schema declaration library with rigorous (and correct!) inferred types, incredible developer experience, and a few killer features missing from the existing libraries.

  • Zero dependencies (5kb compressed)
  • Immutability; methods (i.e. .optional() return a new instance
  • Concise, chainable interface
  • Functional approach ("Parse, don't validate!")

Primitives

You can create a Zod schema for any TypeScript primitive.

import * as z from 'zod';

// primitive values
z.string();
z.number();
z.bigint();
z.boolean();
z.date();

// empty types
z.undefined();
z.null();

// catch-all types
z.any();
z.unknown();

Literals

const tuna = z.literal('tuna');
const twelve = z.literal(12);
const tru = z.literal(true);

Currently there is no support for Date or bigint literals in Zod. If you have a use case for this feature, please file an issue.

Validation

Parsing

.parse(data:unknown)

Given any Zod schema, you can call its .parse method to check data is valid. If it is, a value is returned with full type information! Otherwise, an error is thrown.

IMPORTANT: As of Zod 1.4, the value returned by .parse is the same variable you passed in. Previously it returned a deep clone. The only exception to this is Promise schemas, which return a new Promise for reasons explained in the documentation.

const stringSchema = z.string();
stringSchema.parse('fish'); // => returns "fish"
stringSchema.parse(12); // throws Error('Non-string type: number');

Type guards

.check(data:unknown)

You can also use a Zod schema as a type guard using the schema's .check() method, like so:

const stringSchema = z.string();
const blob: any = 'Albuquerque';
if (stringSchema.check(blob)) {
  // blob is now of type `string`
  // within this if statement
}

You can use the same method to check for invalid data:

const stringSchema = z.string();

const process = (blob: any) => {
  if (!stringSchema.check(blob)) {
    throw new Error('Not a string');
  }

  // blob is now of type `string`
  // underneath the if statement
};

To learn more about error handling with Zod, jump to Errors.

Custom validation

.refine(validator: (data:T)=>any, err?: string)

Zod was designed to mirror TypeScript as closely as possible. But there are many so-called "refinement types" you may wish to check for that can't be represented in TypeScript's type system. For instance: checking that a number is an Int or that a string is a valid email address.

For this instances, you can define custom a validation check on any Zod schema with .refine:

const myString = z.string().refine(val => val.length <= 255, "String can't be more than 255 characters");

Here is the type signature for .refine:

As you can see, .refine takes two arguments.

  1. The first is the validation function. This function takes one input (of type T — the inferred type of the schema) and returns any. Any truthy value will pass validation. (Prior to zod@1.6.2 the validation function had to return a boolean.)
  2. The second argument is a custom error message. Read more about error handling in Zod here

Check out validator.js for a bunch of useful string validation functions.

Type inference

You can extract the TypeScript type of any schema with z.infer<typeof mySchema>.

const A = z.string();
type A = z.infer<typeof A>; // string

const u: A = 12; // TypeError
const u: A = 'asdf'; // compiles

We'll include examples of inferred types throughout the rest of the documentation.

Strings

There are a handful of string-specific validations.

All of these validations allow you to optionally specify a custom error message.

z.string().min(5);
z.string().max(5);
z.string().length(5);
z.string().email();
z.string().url();

Custom error messages

Like .refine, the final argument accepts a custom error message.

z.string().min(5, 'Must be 5 or more characters long');
z.string().max(5, 'Must be 5 or fewer characters long');
z.string().length(5, 'Must be exactly 5 characters long');
z.string().email('Invalid email address.');
z.string().url('Invalid url');

To see the email and url regexes, check out this file. To use a more advanced method, use a custom refinement.

Numbers

There are a handful of number-specific validations.

All of these validations allow you to optionally specify a custom error message as a final string argument.

z.number().min(5);
z.number().max(5);

z.number().int(); // value must be an integer

z.number().positive(); //     > 0
z.number().nonnegative(); //  >= 0
z.number().negative(); //     < 0
z.number().nonpositive(); //  <= 0

Objects

// all properties are required by default
const dogSchema = z.object({
  name: z.string(),
  neutered: z.boolean(),
});

type Dog = z.infer<typeof dogSchema>;
/* 
equivalent to:
type Dog = { 
  name:string; 
  neutered: boolean;
}
*/

const cujo = dogSchema.parse({
  name: 'Cujo',
  neutered: true,
}); // passes, returns Dog

const fido: Dog = {
  name: 'Fido',
}; // TypeError: missing required property `neutered`

.shape property

Use .shape to access an object schema's property schemas.

const Location = z.object({
  latitude: z.number(),
  longitude: z.number(),
});

const Business = z.object({
  location: Location,
});

Business.shape.location; // => Location schema

Merging

You can combine two object schemas with .merge, like so:

const BaseTeacher = z.object({ subjects: z.array(z.string()) });
const HasID = z.object({ id: z.string() });

const Teacher = BaseTeacher.merge(HasId);
type Teacher = z.infer<typeof Teacher>; // => { subjects: string[], id: string }

You're able to fluently chain together many .merge calls as well:

// chaining mixins
const Teacher = BaseTeacher.merge(HasId)
  .merge(HasName)
  .merge(HasAddress);

IMPORTANT: the schema returned by .merge is the intersection of the two schemas. The schema passed into .merge does not "overwrite" properties of the original schema. To demonstrate:

const Obj1 = z.object({ field: z.string() });
const Obj2 = z.object({ field: z.number() });

const Merged = Obj1.merge(Obj2);

type Merged = z.infer<typeof merged>;
// => { field: never }
// because no type can simultaneously be both a string and a number

To "overwrite" existing keys, use .augment (documented below).

Augmentation

You can augment an object schema with the .augment method.

const Animal = z
  .object({
    species: z.string(),
  })
  .augment({
    population: z.number(),
  });

⚠️ You can use .augment to overwrite fields! Be careful with this power!

// overwrites `species`
const ModifiedAnimal = Animal.augment({
  species: z.array(z.string()),
});

// => { population: number, species: string[] }

Masking

Object masking is one of Zod's killer features. It lets you create slight variations of your object schemas easily and succinctly. Inspired by TypeScript's built-in Pick and Omit utility types, all Zod object schemas have .pick and .omit methods that return a "masked" version of the schema.

const Recipe = z.object({
  id: z.string(),
  name: z.string(),
  ingredients: z.array(z.string()),
});

To only keep certain keys, use .pick.

const JustTheName = Recipe.pick({ name: true });

type JustTheName = z.infer<typeof JustTheName>;
// => { name: string }

To remove certain keys, use .omit.

const NoIDRecipe = Recipe.omit({ id: true });

type NoIDRecipe = z.infer<typeof NoIDRecipe>;
// => { name: string, ingredients: string[] }

This is useful for database logic, where endpoints often accept as input slightly modified versions of your database schemas. For instance, the input to a hypothetical createRecipe endpoint would accept the NoIDRecipe type, since the ID will be generated by your database automatically.

This is a vital feature for implementing typesafe backend logic, yet as far as I know, no other validation library (yup, Joi, io-ts, runtypes, class-validator, ow...) offers similar functionality as of this writing (April 2020). This is one of the must-have features that inspired the creation of Zod.

Partials

Inspired by the built-in TypeScript utility type Partial, all Zod object schemas have a .partial method that makes all properties optional.

Starting from this object:

const user = z.object({
  username: z.string(),
  location: z.object({
    latitude: z.number(),
    longitude: z.number(),
  }),
});
/*
  { username: string, location: { city: number, state: number } }
*/

We can create a partial version:

const partialUser = user.partial();
/*
{ 
  username?: string | undefined,
  location?: {
    city: number;
    state: number;
  } | undefined
}
*/

// equivalent to:
const partialUser = z.object({
  username: user.shape.username.optional(),
  location: user.shape.location.optional(),
});

Or you can use .deepPartial:

const deepPartialUser = user.deepPartial();

/* 
{
  username?: string | undefined, 
  location?: {
    latitude?: number | undefined;
    longitude?: number | undefined;
  } | undefined
}
*/

Important limitation: deep partials only work as expected in hierarchies of object schemas. It also can't be used on recursive schemas currently, since creating a recursive schema requires casting to the generic ZodType type (which doesn't include all the methods of the ZodObject class). Currently an improved version of Zod is under development that will have better support for recursive schemas.

Unknown keys

By default, Zod object schemas do not allow unknown keys!

const dogSchema = z.object({
  name: z.string(),
  neutered: z.boolean(),
});

dogSchema.parse({
  name: 'Spot',
  neutered: true,
  color: 'brown',
}); // Error(`Unexpected keys in object: 'color'`)

This is an intentional decision to make Zod's behavior consistent with TypeScript. Consider this:

type Dog = z.infer<typeof dogSchema>;

const spot: Dog = {
  name: 'Spot',
  neutered: true,
  color: 'brown',
};
// TypeError: Object literal may only specify known
// properties, and 'color' does not exist in type Dog

TypeScript doesn't allow unknown keys when assigning to an object type, so neither does Zod (by default). If you want to allow this, just call the .nonstrict() method on any object schema:

const dogSchemaNonstrict = dogSchema.nonstrict();

dogSchemaNonstrict.parse({
  name: 'Spot',
  neutered: true,
  color: 'brown',
}); // passes

This change is reflected in the inferred type as well:

type NonstrictDog = z.infer<typeof dogSchemaNonstrict>;
/*
{
  name:string; 
  neutered: boolean;
  [k:string]: any;
} 
*/

Records

Record schemas are used to validate types such as this:

type NumberCache = { [k: string]: number };

If you want to validate that all the values of an object match some schema, without caring about the keys, you should use a Record.

const User = z.object({
  name: z.string(),
});

const UserStore = z.record(User);

type UserStore = z.infer<typeof UserStore>;
// => { [k: string]: User }

This is particularly useful for storing or caching items by ID.

const userStore: UserStore = {};

userStore['77d2586b-9e8e-4ecf-8b21-ea7e0530eadd'] = {
  name: 'Carlotta',
}; // passes

userStore['77d2586b-9e8e-4ecf-8b21-ea7e0530eadd'] = {
  whatever: 'Ice cream sundae',
}; // TypeError

And of course you can call .parse just like any other Zod schema.

UserStore.parse({
  user_1328741234: { name: 'James' },
}); // => passes

A note on numerical keys

You may have expected z.record() to accept two arguments, one for the keys and one for the values. After all, TypeScript's built-in Record type does: Record<KeyType, ValueType>. Otherwise, how do you represent the TypeScript type Record<number, any> in Zod?

As it turns out, TypeScript's behavior surrounding [k: number] is a little unintuitive:

const testMap: { [k: number]: string } = {
  1: 'one',
};

for (const key in testMap) {
  console.log(`${key}: ${typeof key}`);
}
// prints: `1: string`

As you can see, JavaScript automatically casts all object keys to strings under the hood.

Since Zod is trying to bridge the gap between static and runtime types, it doesn't make sense to provide a way of creating a record schema with numerical keys, since there's no such thing as a numerical key in runtime JavaScript.

Arrays

const dogsList = z.array(dogSchema);

dogsList.parse([{ name: 'Fido', age: 4, neutered: true }]); // passes

dogsList.parse([]); // passes

Non-empty lists

const nonEmptyDogsList = z.array(dogSchema).nonempty();
nonEmptyDogsList.parse([]); // throws: "Array cannot be empty"

Length validations

// must contain 5 or more items
z.array(z.string()).min(5);

// must contain 5 or fewer items
z.array(z.string()).max(5);

// must contain exactly 5 items
z.array(z.string()).length(5);

Unions

Zod includes a built-in z.union method for composing "OR" types.

const stringOrNumber = z.union([z.string(), z.number()]);

stringOrNumber.parse('foo'); // passes
stringOrNumber.parse(14); // passes

Optional types

Unions are the basis for defining optional schemas. An "optional string" is just the union of string and undefined.

const A = z.union([z.string(), z.undefined()]);

A.parse(undefined); // => passes, returns undefined
type A = z.infer<typeof A>; // string | undefined

Zod provides a shorthand way to make any schema optional:

const B = z.string().optional(); // equivalent to A

const C = z.object({
  username: z.string().optional(),
});
type C = z.infer<typeof C>; // { username?: string | undefined };

Nullable types

Similarly, you can create nullable types like so:

const D = z.union([z.string(), z.null()]);

Or you can use the shorthand .nullable():

const E = z.string().nullable(); // equivalent to D
type E = z.infer<typeof D>; // string | null

You can create unions of any two or more schemas.

/* Custom Union Types */

const F = z
  .union([z.string(), z.number(), z.boolean()])
  .optional()
  .nullable();

F.parse('tuna'); // => tuna
F.parse(42); // => 42
F.parse(true); // => true
F.parse(undefined); // => undefined
F.parse(null); // => null
F.parse({}); // => throws Error!

type F = z.infer<typeof F>; // string | number | boolean | undefined | null;

Enums

An enum is just a union of string literals, so you can "build your own enum" like this:

const FishEnum = z.union([z.literal('Salmon'), z.literal('Tuna'), z.literal('Trout')]);

FishEnum.parse('Salmon'); // => "Salmon"
FishEnum.parse('Flounder'); // => throws

But for convenience Zod provides a built-in z.enum() function, like so:

const FishEnum = z.enum(['Salmon', 'Tuna', 'Trout']);
type FishEnum = z.infer<typeof FishEnum>;
// 'Salmon' | 'Tuna' | 'Trout'

You need to either need to pass the literal array directly into z.enum:

const FishEnum = z.enum(['Salmon', 'Tuna', 'Trout']);

or use as const (introduced in TypeScript 3.4):

const fishTypes = ['Salmon', 'Tuna', 'Trout'] as const;
const FishEnum = z.enum(fishTypes);

otherwise type inference won't work properly.

Autocompletion

You can get autocompletion of enum values with the .Values property of an enum schema:

FishEnum.Values.Salmon; // => autocompletes

FishEnum.Values;
/* 
=> {
  Salmon: "Salmon",
  Tuna: "Tuna",
  Trout: "Trout",
} 
*/

Intersections

Intersections are useful for creating "logical AND" types.

const a = z.union([z.number(), z.string()]);
const b = z.union([z.number(), z.boolean()]);

const c = z.intersection(a, b);
type c = z.infer<typeof C>; // => number

const stringAndNumber = z.intersection(z.string(), z.number());
type Never = z.infer<typeof stringAndNumber>; // => never

This is particularly useful for defining "schema mixins" that you can apply to multiple schemas.

const HasId = z.object({
  id: z.string(),
});

const BaseTeacher = z.object({
  name: z.string(),
});

const Teacher = z.intersection(BaseTeacher, HasId);

type Teacher = z.infer<typeof Teacher>;
// { id:string; name:string };

Tuples

These differ from arrays in that they have a fixed number of elements, and each element can have a different type.

const athleteSchema = z.tuple([
  // takes an array of schemas
  z.string(), // name
  z.number(), // jersey number
  z.object({
    pointsScored: z.number(),
  }), // statistics
]);

type Athlete = z.infer<typeof athleteSchema>;
// type Athlete = [string, number, { pointsScored: number }]

Recursive types

You can define a recursive schema in Zod, but because of a limitation of TypeScript, their type can't be statically inferred. If you need a recursive Zod schema you'll need to define the type definition manually, and provide it to Zod as a "type hint".

interface Category {
  name: string;
  subcategories: Category[];
}

const Category: z.ZodType<Category> = z.lazy(() =>
  z.object({
    name: z.string(),
    subcategories: z.array(Category),
  }),
);

Category.parse({
  name: 'People',
  subcategories: [
    {
      name: 'Politicians',
      subcategories: [{ name: 'Presidents', subcategories: [] }],
    },
  ],
}); // passes

Unfortunately this code is a bit duplicative, since you're declaring the types twice: once in the interface and again in the Zod definition.

If your schema has lots of primitive fields, there's a way of reducing the amount of duplication:

// define all the non-recursive stuff here
const BaseCategory = z.object({
  name: z.string(),
  tags: z.array(z.string()),
  itemCount: z.number(),
});

// create an interface that extends the base schema
interface Category extends z.infer<typeof BaseCategory> {
  subcategories: Category[];
}

// merge the base schema with
// a new Zod schema containing relations
const Category: z.ZodType<Category> = BaseCategory.merge(
  z.object({
    subcategories: z.lazy(() => z.array(Category)),
  }),
);

JSON type

There isn't a built-in method for validating any JSON, because representing that requires recursive type aliases (a feature that TypeScript started supporting with version 3.7). In order to support a wider range of TypeScript versions (see the top of the README for details) we aren't providing a JSON type out of the box at this time. If you want to validate JSON and you're using TypeScript 3.7+, you can use this snippet to achieve that:

type Literal = boolean | null | number | string;
type Json = Literal | { [key: string]: Json } | Json[];
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
const jsonSchema: z.ZodType<Json> = z.lazy(() => z.union([Literal, z.array(Json), z.record(Json)]));

jsonSchema.parse({
  // ...
});

Thanks to ggoodman for suggesting this.

Cyclical objects

Validation still works as expected even when there are cycles in the data.

const cyclicalCategory: any = {
  name: 'Category A',
};

// creating a cycle
cyclicalCategory.subcategories = [cyclicalCategory];

const parsedCategory = Category.parse(cyclicalCategory); // parses successfully

parsedCategory.subcategories[0].subcategories[0].subcategories[0];
// => parsedCategory: Category;

Promises

As of zod@1.3, there is also support for Promise schemas!

const numberPromise = z.promise(z.number());

"Parsing" works a little differently with promise schemas. Validation happens in two parts:

  1. Zod synchronously checks that the input is an instance of Promise (i.e. an object with .then and .catch methods.).
  2. Zod waits for the promise to resolve then validates the resolved value.
numberPromise.parse('tuna');
// ZodError: Non-Promise type: string

numberPromise.parse(Promise.resolve('tuna'));
// => Promise<number>

const test = async () => {
  await numberPromise.parse(Promise.resolve('tuna'));
  // ZodError: Non-number type: string

  await numberPromise.parse(Promise.resolve(3.14));
  // => 3.14
};

Non-native promise implementations

When "parsing" a promise, Zod checks that the passed value is an object with .then and .catch methods — that's it. So you should be able to pass non-native Promises (Bluebird, etc) into z.promise(...).parse with no trouble. One gotcha: the return type of the parse function will be a native Promise, so if you have downstream logic that uses non-standard Promise methods, this won't work.

Function schemas

Zod also lets you define "function schemas". This makes it easy to validate the inputs and outputs of a function without intermixing your validation code and "business logic".

You can create a function schema with z.function(args, returnType) which accepts these arguments.

  • args: ZodTuple The first argument is a tuple (created with z.tuple([...]) and defines the schema of the arguments to your function. If the function doesn't accept arguments, you can pass an empty tuple (z.tuple([])).
  • returnType: any Zod schema The second argument is the function's return type. This can be any Zod schema.
const args = z.tuple([z.string()]);

const returnType = z.number();

const myFunction = z.function(args, returnType);
type myFunction = z.infer<typeof myFunction>;
// => (arg0: string)=>number

Function schemas have an .implement() method which accepts a function as input and returns a new function.

const myValidatedFunction = myFunction.implement(x => {
  // TypeScript knows x is a string!
  return x.trim().length;
});

myValidatedFunction now automatically validates both its inputs and return value against the schemas provided to z.function. If either is invalid, the function throws.

This way you can confidently write application logic in a "validated function" without worrying about invalid inputs, scattering schema.validate() calls in your endpoint definitions,or writing duplicative types for your functions.

Here's a more complex example showing how to write a typesafe API query endpoint:

const args = z.tuple([
  z.object({ id: z.string() }), // get by ID
]);

const returnType = z.promise(
  z.object({
    id: string(),
    name: string(),
  }),
);

const FetcherEndpoint = z.function(args, returnType);

const getUserByID = FetcherEndpoint.validate(args => {
  args; // => { id: string }

  const user = await User.findByID(args.id);

  // TypeScript statically verifies that value returned by
  // this function is of type Promise<{ id: string; name: string; }>
  return 'salmon'; // TypeError

  return user; // success
});

This is particularly useful for defining HTTP or RPC endpoints that accept complex payloads that require validation. Moreover, you can define your endpoints once with Zod and share the code with both your client and server code to achieve end-to-end type safety.

// Express example
server.get(`/user/:id`, async (req, res) => {
  const user = await getUserByID({ id: req.params.id }).catch(err => {
    res.status(400).send(err.message);
  });

  res.status(200).send(user);
});

Errors

Zod includes a custom Error subclass called ZodError. All validation errors thrown by Zod are instances of ZodError.

A ZodError instance has an errors property of type

// ZodError#errors
{
  path: (string | number)[],
  message: string
}[]

This array represents all errors Zod encounters when attempting to parse a value.

const person = z.object({
  name: {
    first: z.string(),
    last: z.string(),
  },
  age: z.number(),
  address: z.array(z.string()),
});

try {
  person.parse({
    name: { first: 'Dave', last: 42 },
    age: 'threeve',
    address: ['123 Maple Street', {}],
  });
} catch (err) {
  if (err instanceof ZodError) {
    console.log(JSON.stringify(err.errors));
    /*
      [
        {
          "path": [ "name", "last" ],
          "message": "Non-string type: number"
        },
        {
          "path": [ "age" ],
          "message": "Non-number type: string"
        },
        {
          "path": [ "address", 1 ],
          "message": "Non-string type: object"
        }
      ]
    */

    // err.message returns a formatted error message
    console.log(err.message);
    /*
      `name.last`: Non-string type: number
      `age`: Non-number type: string
      `address.1`: Non-string type: object
    */
  } else {
    // should never happen
  }
}

Comparison

There are a handful of other widely-used validation libraries, but all of them have certain design limitations that make for a non-ideal developer experience.

Joi

https://github.com/hapijs/joi

Doesn't support static type inference. Boo. 😕

Yup

https://github.com/jquense/yup

Yup is a full-featured library that was implemented first in vanilla JS, with TypeScript typings added later.

Yup supports static type inference! But unfortunately the inferred types aren't actually correct.

Incorrect object typing (now fixed!)

This issue was fixed on May 19, 2020 (here).

Currently, the yup package treats all object properties as optional by default:.

const schema = yup.object({
  asdf: yup.string(),
});
schema.validate({}); // passes

Yet the inferred type indicates that all properties are required:

type SchemaType = yup.InferType<typeof schema>;
// returns { asdf: string }
// should be { asdf?: string }
Unintuitive .required() behavior

In general, Yup's interpretation of .required() is odd and non-standard. Instead of meaning "not undefined", Yup uses it to mean "not empty". So yup.string().required() will not accept an empty string, and yup.array(yup.string()).required() will not accept an empty array. For Zod arrays there is a dedicated .nonempty() method to indicate this, or you can implement it with a custom validator.

const numList = yup
  .array()
  .of(yup.string())
  .required();

// interpreted as a non-empty list
numList.validateSync([]); // fails

// yet the inferred type doesn't reflect this
type NumList = yup.InferType<typeof numList>;
// returns 		string[]
// should be 	[string,...string[]]
Unions and intersections

Finally, Yup doesn't support any generic union or intersection operator.

io-ts

https://github.com/gcanti/io-ts

io-ts is an excellent library by gcanti. The API of io-ts heavily inspired the design of Zod.

In our experience, io-ts prioritizes functional programming purity over developer experience in many cases. This is a valid and admirable design goal, but it makes io-ts particularly hard to integrate into an existing codebase with a more procedural or object-oriented bias.

For instance, consider how to define an object with optional properties in io-ts:

import * as t from 'io-ts';

const A = t.type({
  foo: t.string,
});

const B = t.partial({
  bar: t.number,
});

const C = t.intersection([A, B]);

type C = t.TypeOf<typeof C>;
// returns { foo: string; bar?: number | undefined }

You must define the required and optional props in separate object validators, pass the optionals through t.partial (which marks all properties as optional), then combine them with t.intersection.

Consider the equivalent in Zod:

const C = z.object({
  foo: z.string(),
  bar: z.string().optional(),
});

type C = z.infer<typeof C>;
// returns { foo: string; bar?: number | undefined }

This more declarative API makes schema definitions vastly more concise.

io-ts also requires the use of gcanti's functional programming library fp-ts to parse results and handle errors. This is another fantastic resource for developers looking to keep their codebase strictly functional. But depending on fp-ts necessarily comes with a lot of intellectual overhead; a developer has to be familiar with functional programming concepts, fp-ts's nomenclature, and the Either monad to do a simple schema validation. It's just not worth it for many people.

Runtypes

https://github.com/pelotom/runtypes

Good type inference support, but limited options for object type masking (no .pick, .omit, .augment, etc.). No support for Records (their Record is equivalent to Zod's object). They DO support branded and readonly types, which Zod does not.

Ow

https://github.com/sindresorhus/ow

Ow is focused on function input validation. It's a library that makes it easy to express complicated assert statements, but it doesn't let you parse untyped data. They support a much wider variety of types; Zod has a nearly one-to-one mapping iwhtwith TypeScript's type system, whereas ow lets you validate several highly-specific types out of the box (e.g. int32Array, see full list in their README).

If you want to validate function inputs, use function schemas in Zod! It's a much simpler approach that lets you reuse a function type declaration without repeating yourself (namely, copy-pasting a bunch of ow assertions at the beginning of every function). Also Zod lets you validate your return types as well, so you can be sure there won't be any unexpected data passed downstream.

Changelog

zod versionrelease notes
zod@1.7Added several built-in validators to string, number, and array schemas. Calls to .refine now return new instance.
zod@1.5Any and unknown types
zod@1.4Refinement types (.refine), .parse no longer returns deep clone
zod@1.3Promise schemas
zod@1.2.6.parse accepts unknown, bigint schemas
zod@1.2.5.partial and .deepPartial on object schemas
zod@1.2.3Date schemas
zod@1.2.0.pick, .omit, and .augment on object schemas
zod@1.1.0Records
zod@1.0.11.nonstrict
zod@1.0.10Type assertions with .check
zod@1.0.4Empty tuples
zod@1.0.0Type assertions, literals, enums, detailed error reporting
zod@1.0.0Initial release
next@jimp/typesgraphql-request@quiltt/ui@unavi/gltf-extensions@yesmaintenance/scripts@xata.io/codegen@xata.io/clifirst-library-saroj@ben-ryder/lfb-server@bkalendar/core@lattice-engine/gltf@leonardodipace/x@modern-js/builder-shared@titicaca/ntk-koa-helpers@tokdaniel/superfluid-widget@trailrun/node@neuvernetzung/cms-env@neuvernetzung/cms-plugin-projects@neuvernetzung/cms-types@neuvernetzung/cms@neuvernetzung/cms-cli@zeeum/carbon@klicker-uzh/graphql@unding/studio@unding/kit@unding/schema@razaman2/firestore@darlyn1234/zkrill-api-darl@falchion-studios/claymore-api@feltcoop/gro@fernapi/fernastro-no-undicibetter-connectorblockbuildbys-container@dingyi222666/chathub-llm-core@gmjs/lib-util@gmjs/mongo-util@graphql-hive/clicreate-thoo-appcreate-thoo-app-auth@backstage/cli@orionprotocol/sdk@tinacms/cli@thirdweb-dev/sdkappist@thirdweb-dev/auth@fern-typescript/sdk-generator-cli@fern-typescript/plugin-loader@fern-typescript/express-generator-cli@fern-typescript/helper-commons@fern-typescript/helper-managermjimage@trycreo/next-starter@uponco/admin-uimatrix-artifact-react-io-ui-components@evangodon/lrkeat-nodeembedable-sdk@stackshirts/appreach-version-managernano-block-factorybraty-commonzod-jsonapi@jsonapi-mapper/corenpm-memesneo-apisdfsdfdnftcard-design-systemstgandalfdry-express-responseslintan@bronzitejs/frameworkzod-vue@slide-web3/thirdweb-typescript-sdk@dometag/utils@discursa/components@discursa/corebedpackdldldlnumption@dgroux/api-framework@communityboss/service-provider-validators@darthrommy/fetcheskold-js@bpinternal/cli@ifaxity/semantic-release-dotnet@ifaxity/semantic-release-nugetdynamorph@yardstik/embedable-sdk@manuscript-org/glue@mintlify/mint-validation@lcnc/ts-to-zod@wesjet/maker@runes-security/client-sdk@runes-security/shared-models@quantform/studio@remix-kawaii/language@lvl-up/lib-core@ethang/util-typescript
3.21.5-alpha.0

11 months ago

3.22.0

9 months ago

3.22.2

8 months ago

3.22.1

9 months ago

3.22.4

7 months ago

3.22.3

7 months ago

3.20.4

1 year ago

3.20.3

1 year ago

3.20.6

1 year ago

3.20.5

1 year ago

3.21.1

1 year ago

3.21.0

1 year ago

3.21.3

1 year ago

3.21.2

1 year ago

3.21.4

1 year ago

3.20.4-beta.0

1 year ago

3.20.0

1 year ago

3.20.2

1 year ago

3.20.1

1 year ago

3.20.0-beta.0

1 year ago

3.19.0-beta.0

2 years ago

3.19.0

2 years ago

3.19.1

2 years ago

3.17.6

2 years ago

3.17.5

2 years ago

3.17.8

2 years ago

3.17.7

2 years ago

3.17.9

2 years ago

3.17.0

2 years ago

3.17.2

2 years ago

3.17.4

2 years ago

3.17.3

2 years ago

3.18.0

2 years ago

3.17.10

2 years ago

3.14.5

2 years ago

3.15.0

2 years ago

3.15.1

2 years ago

3.16.1

2 years ago

3.16.0

2 years ago

3.14.1

2 years ago

3.14.0

2 years ago

3.14.3

2 years ago

3.14.2

2 years ago

3.14.4

2 years ago

3.13.2

2 years ago

3.13.4

2 years ago

3.13.3

2 years ago

3.12.1

2 years ago

3.13.0

2 years ago

3.12.0

2 years ago

3.11.6

3 years ago

3.11.5

3 years ago

3.11.4

3 years ago

3.11.3

3 years ago

3.10.1

3 years ago

3.10.0

3 years ago

3.10.3

3 years ago

3.10.2

3 years ago

3.11.0

3 years ago

3.11.2

3 years ago

3.11.1

3 years ago

3.10.0-beta.1

3 years ago

3.9.8

3 years ago

3.9.7

3 years ago

3.9.6

3 years ago

3.9.5

3 years ago

3.9.3

3 years ago

3.9.2

3 years ago

3.9.4

3 years ago

3.9.1

3 years ago

3.9.0

3 years ago

3.8.2

3 years ago

3.8.2-alpha.6

3 years ago

3.8.2-alpha.5

3 years ago

3.8.2-alpha.4

3 years ago

3.8.2-alpha.3

3 years ago

3.8.2-alpha.2

3 years ago

3.8.2-alpha.1

3 years ago

3.8.0

3 years ago

3.8.1

3 years ago

3.7.3

3 years ago

3.7.2

3 years ago

3.6.1

3 years ago

3.6.0

3 years ago

3.5.3

3 years ago

3.5.2

3 years ago

3.5.4

3 years ago

3.7.1

3 years ago

3.7.0

3 years ago

3.5.1

3 years ago

3.5.0

3 years ago

3.4.0

3 years ago

3.4.2

3 years ago

3.4.1

3 years ago

3.3.4

3 years ago

3.3.3

3 years ago

3.3.1

3 years ago

3.3.0

3 years ago

3.3.2

3 years ago

4.0.0-beta.1

3 years ago

3.2.0

3 years ago

3.2.0-beta

3 years ago

1.11.16

3 years ago

1.11.17

3 years ago

1.11.14

3 years ago

1.11.15

3 years ago

3.0.0-beta.1

3 years ago

3.0.0-beta.3

3 years ago

3.0.0-beta.2

3 years ago

3.0.0-beta.4

3 years ago

3.1.0

3 years ago

3.0.2

3 years ago

3.0.1

3 years ago

3.0.0

3 years ago

3.0.0-alpha.36

3 years ago

3.0.0-alpha.35

3 years ago

3.0.0-alpha.38

3 years ago

3.0.0-alpha.37

3 years ago

3.0.0-alpha.39

3 years ago

3.0.0-alpha.34

3 years ago

3.0.0-alpha.40

3 years ago

3.0.0-alpha.33

3 years ago

3.0.0-alpha.9

3 years ago

1.11.13

3 years ago

3.0.0-alpha.14

3 years ago

3.0.0-alpha.13

3 years ago

3.0.0-alpha.16

3 years ago

3.0.0-alpha.15

3 years ago

3.0.0-alpha.18

3 years ago

3.0.0-alpha.17

3 years ago

3.0.0-alpha.19

3 years ago

3.0.0-alpha.10

3 years ago

3.0.0-alpha.12

3 years ago

3.0.0-alpha.11

3 years ago

3.0.0-alpha.25

3 years ago

3.0.0-alpha.24

3 years ago

3.0.0-alpha.27

3 years ago

3.0.0-alpha.26

3 years ago

3.0.0-alpha.29

3 years ago

3.0.0-alpha.28

3 years ago

3.0.0-alpha.21

3 years ago

3.0.0-alpha.20

3 years ago

3.0.0-alpha.22

3 years ago

3.0.0-alpha.30

3 years ago

3.0.0-alpha.32

3 years ago

3.0.0-alpha.31

3 years ago

3.0.0-alpha.8

3 years ago

3.0.0-alpha.7

3 years ago

3.0.0-alpha.6

3 years ago

1.11.12

3 years ago

3.0.0-alpha.5

3 years ago

2.0.0-beta.30

3 years ago

3.0.0-alpha.4

3 years ago

3.0.0-alpha.3

3 years ago

3.0.0-alpha.2

3 years ago

3.0.0-alpha.1

3 years ago

2.0.0-beta.29

3 years ago

2.0.0-beta.28

3 years ago

2.0.0-beta.26

3 years ago

2.0.0-beta.27

3 years ago

2.0.0-beta.25

3 years ago

1.11.11

3 years ago

2.0.0-beta.24

3 years ago

2.0.0-beta.23

3 years ago

2.0.0-beta.22

3 years ago

2.0.0-alpha.22

3 years ago

2.0.0-beta.21

3 years ago

2.0.0-beta.20

3 years ago

2.0.0-beta.19

3 years ago

2.0.0-alpha.19

3 years ago

2.0.0-alpha.18

3 years ago

2.0.0-beta.18

3 years ago

2.0.0-beta.17

3 years ago

1.11.10

3 years ago

2.0.0-beta.16

4 years ago

2.0.0-beta.15

4 years ago

2.0.0-beta.14

4 years ago

2.0.0-beta.13

4 years ago

2.0.0-beta.12

4 years ago

2.0.0-beta.11

4 years ago

2.0.0-beta.10

4 years ago

2.0.0-beta.9

4 years ago

2.0.0-beta.8

4 years ago

2.0.0-beta.7

4 years ago

1.11.9

4 years ago

2.0.0-beta.6

4 years ago

2.0.1-alpha.3

4 years ago

2.0.1-alpha.2

4 years ago

2.0.1-alpha.1

4 years ago

2.0.0-beta.2

4 years ago

2.0.0-beta.1

4 years ago

2.0.0-beta.5

4 years ago

2.0.0-beta.4

4 years ago

2.0.0-beta.3

4 years ago

1.11.8

4 years ago

1.11.7

4 years ago

1.11.6

4 years ago

1.11.5

4 years ago

1.11.4

4 years ago

1.11.3

4 years ago

1.11.2

4 years ago

1.11.1

4 years ago

1.11.0

4 years ago

1.10.4

4 years ago

1.10.3

4 years ago

1.11.0-alpha.6

4 years ago

1.10.2

4 years ago

1.10.2-canary

4 years ago

1.11.0-alpha.5

4 years ago

1.11.0-alpha.1

4 years ago

1.11.0-alpha.3

4 years ago

1.11.0-alpha.2

4 years ago

1.10.1

4 years ago

1.10.0

4 years ago

1.9.2

4 years ago

1.9.1

4 years ago

1.10.0-alpha.1

4 years ago

1.10.0-alpha.2

4 years ago

1.8.0

4 years ago

1.8.0-beta.1

4 years ago

1.8.0-beta.3

4 years ago

1.8.0-beta.2

4 years ago

1.9.0

4 years ago

1.7.2

4 years ago

2.0.0-alpha.7

4 years ago

2.0.0-alpha.6

4 years ago

2.0.0-alpha.3

4 years ago

2.0.0-alpha.4

4 years ago

2.0.0-alpha.5

4 years ago

2.0.0-alpha.2

4 years ago

2.0.0-alpha.1

4 years ago

1.7.1

4 years ago

1.6.2

4 years ago

1.7.0

4 years ago

1.6.1

4 years ago

1.6.0

4 years ago

1.5.0

4 years ago

1.4.1

4 years ago

1.4.0

4 years ago

1.3.0

4 years ago

1.2.6

4 years ago

1.2.5

4 years ago

1.2.4

4 years ago

1.2.3

4 years ago

1.2.2

4 years ago

1.2.1

4 years ago

1.2.0

4 years ago

1.1.2

4 years ago

1.1.1

4 years ago

1.1.0

4 years ago

1.0.15

4 years ago

1.0.14

4 years ago

1.0.13

4 years ago

1.0.12

4 years ago

1.0.11

4 years ago

1.0.10

4 years ago

1.0.9

4 years ago

1.0.8

4 years ago

1.0.7

4 years ago

1.0.6

4 years ago

1.0.5

4 years ago

1.0.4

4 years ago

1.0.2

4 years ago

1.0.1

4 years ago

1.0.0

4 years ago