Zod logo

@zod/core

If you are build a library on top of Zod, you should build against the @zod/core package. This package exports the core classes that are extended by zod and @zod/mini.

The @zod/core package implements Zod's core parsing logic and additional utilities. It is not intended to be used directly; instead it's designed to be extended by other packages like zod and @zod/mini. It implements:

  • base schema class $ZodType and subclasses like $ZodString, etc.
  • base check class $ZodCheck and subclasses like $ZodCheckMultipleOf, etc.
  • base error class $ZodError and shared issue formats
  • utility types and functions

For simplicity, the zod and @zod/mini packages re-export their @zod/core dependency as z.core.

import * as z from "zod";
 
z.core.$ZodString;
// the base class for string schemas

Versioning

Though Zod 4 is in beta, the @zod/core package is published as latest. The plan is to keep @zod/core in the It will remain in the v0.x version range ("initial development" according to semver) until the first stable release of Zod 4, at which point v1.0.0 will be released.

Schemas

The base class for all Zod schemas is $ZodType. It accepts two generic parameters: Output and Input.

export class $ZodType<Output = unknown, Input = unknown> {
  _zod: { /* internals */}
}

@zod/core exports a number of subclasses that implement some common parsers. A union of all first-party subclasses is exported as z.$ZodTypes.

export type $ZodTypes =
  | $ZodString
  | $ZodNumber
  | $ZodBigInt
  | $ZodBoolean
  | $ZodDate
  | $ZodSymbol
  | $ZodUndefined
  | $ZodNullable
  | $ZodNull
  | $ZodAny
  | $ZodUnknown
  | $ZodNever
  | $ZodVoid
  | $ZodArray
  | $ZodObject
  | $ZodInterface
  | $ZodUnion
  | $ZodIntersection
  | $ZodTuple
  | $ZodRecord
  | $ZodMap
  | $ZodSet
  | $ZodLiteral
  | $ZodEnum
  | $ZodPromise
  | $ZodLazy
  | $ZodOptional
  | $ZodDefault
  | $ZodTemplateLiteral
  | $ZodCustom
  | $ZodTransform
  | $ZodNonOptional
  | $ZodReadonly
  | $ZodNaN
  | $ZodPipe
  | $ZodSuccess
  | $ZodCatch
  | $ZodFile;

All @zod/core subclasses only contain a single property: _zod. This property is an object containing the schemas internals: it's state, implemention, etc. The goal is to make @zod/core as extensible and unopinionated as possible. Other libraries can "build their own Zod" or otherwise integrate Zod's super-optimized parsing logic into their own library by leveraging the subclasses in @zod/core.

The _zod internals property contains some notable properties:

  • .def — The schema's definition: this is the object you pass into the class's constructor to create an instance. It completely describes the schema, and it's JSON-serializable.
    • .def.type — A string representing the schema's type, e.g. "string", "object", "array", etc.
    • .def.checks — An array of checks that are executed by the schema after parsing.
  • .input — A virtual property that "stores" the schema's inferred input type.
  • .output — A virtual property that "stores" the schema's inferred output type.
  • .run() — The schema's internal parser implementation.

If you are implementing a tool (say, a code generator) that must traverse Zod schemas, you can cast any schema to $ZodTypes and use the def property to discriminate between these classes.

export function walk(_schema: z.$ZodType) {
  const schema = _schema as z.$ZodTypes;
  const def = schema._zod.def;
  switch (def.type) {
    case "string": {
      // ...
      break;
    }
    case "object": {
      // ...
      break;
    }
  }
}

There are a number of subclasses of $ZodString that implement various string formats. These are exported as z.$ZodStringFormatTypes.

export type $ZodStringFormatTypes =
  | $ZodGUID
  | $ZodUUID
  | $ZodEmail
  | $ZodURL
  | $ZodEmoji
  | $ZodNanoID
  | $ZodCUID
  | $ZodCUID2
  | $ZodULID
  | $ZodXID
  | $ZodKSUID
  | $ZodISODateTime
  | $ZodISODate
  | $ZodISOTime
  | $ZodISODuration
  | $ZodIPv4
  | $ZodIPv6
  | $ZodBase64
  | $ZodE164
  | $ZodJWT

Checks

Every Zod schema contains an array of checks. These perform post-parsing refinements (and occasionally mutations) that do not affect the inferred type.

const schema = z.string().check(z.email()).check(z.min(5));
// => ZodString
 
schema._zod.def.checks;
// => [ZodCheckEmail, ZodCheckMinLength]

The base class for all Zod checks is $ZodCheck. It accepts a single generic parameter T.

export class $ZodCheck<in T = unknown> {
  // ...
}

@zod/core exports a number of subclasses that perform some common refinements. All first-party subclasses are exported as a union called z.$ZodChecks.

export type $ZodChecks =
  | $ZodCheckLessThan
  | $ZodCheckGreaterThan
  | $ZodCheckMultipleOf
  | $ZodCheckNumberFormat
  | $ZodCheckBigIntFormat
  | $ZodCheckMaxSize
  | $ZodCheckMinSize
  | $ZodCheckSizeEquals
  | $ZodCheckMaxLength
  | $ZodCheckMinLength
  | $ZodCheckLengthEquals
  | $ZodCheckProperty
  | $ZodCheckMimeType
  | $ZodCheckOverwrite
  | $ZodCheckStringFormat

As with schema types, there are a number of subclasses of $ZodCheckStringFormat that implement various string formats.

export type $ZodStringFormatChecks =
  | $ZodCheckRegex
  | $ZodCheckLowerCase
  | $ZodCheckUpperCase
  | $ZodCheckIncludes
  | $ZodCheckStartsWith
  | $ZodCheckEndsWith
  | $ZodGUID
  | $ZodUUID
  | $ZodEmail
  | $ZodURL
  | $ZodEmoji
  | $ZodNanoID
  | $ZodCUID
  | $ZodCUID2
  | $ZodULID
  | $ZodXID
  | $ZodKSUID
  | $ZodISODateTime
  | $ZodISODate
  | $ZodISOTime
  | $ZodISODuration
  | $ZodIPv4
  | $ZodIPv6
  | $ZodBase64
  | $ZodE164
  | $ZodJWT;

You'll notice some of these string format checks overlap with the string format types above. That's because these classes implement both the $ZodCheck and $ZodType interfaces. That is, they can be used as either a check or a type.

Note — If an instance implements $ZodCheck, it gets preprended to the array of checks (.def.checks) during parsing.

// as a type
z.email().parse("user@example.com");
 
// as a check
z.string().check(z.email()).parse("user@example.com")

As with z.$ZodTypes, you can use the ._zod.def.check property to discriminate between these classes.

const check = {} as $ZodChecks;
const def = check._zod.def;
 
switch (def.check) {
  case "less_than":
  case "greater_than":
    // ...
    break;
}

Errors

The base class for all errors in Zod is $ZodError.

For performance reasons, $ZodError does not extend the built-in Error class! So using instanceof Error will return false.

  • The zod package implements a subclass of $ZodError called ZodError with some additional convenience methods.
  • The @zod/mini package directly uses $ZodError
export class $ZodError<T = unknown> implements Error {
 public issues: $ZodIssue[];
}

Issues

The issues property corresponds to an array of $ZodIssue objects. All issues extend the z.$ZodIssueBase interface.

export interface $ZodIssueBase {
  readonly code?: string;
  readonly input?: unknown;
  readonly path: PropertyKey[];
  readonly message: string;
}

Zod defines the following issue subtypes:

export type $ZodIssue =
  | $ZodIssueInvalidType
  | $ZodIssueTooBig
  | $ZodIssueTooSmall
  | $ZodIssueInvalidStringFormat
  | $ZodIssueNotMultipleOf
  | $ZodIssueUnrecognizedKeys
  | $ZodIssueInvalidUnion
  | $ZodIssueInvalidKey
  | $ZodIssueInvalidElement
  | $ZodIssueInvalidValue
  | $ZodIssueCustom;

For details on each type, refer to the implementation.

Best practices

If you're reading this page, you're likely trying to build some kind of tool or library on top of Zod. This section breaks down some best practices for doing so.

  1. If you're just accepted user-defined schemas, use Standard Schema instead

Zod implements the Standard Schema specification, a standard interface for schema libraries to expose their validation logic and inferred types to third-party tools. If your goal is to accept user-defined schemas, extracting their inferred types, and using them to parse data, then Standard Schema is all you need. Refer to the Standard Schema website/docs for more information.

  1. Set up peerDependencies properly!

If your tool accepts Zod schemas from a consumer/user, you should add "@zod/core" to peerDependencies. This lets your users "bring their own Zod". Be as flexible as possible with the version range. For example, if your tool is compatible with @zod/core, you can use the following. This allows your users to bring any version of @zod/core, avoiding accidental duplicate installs.

{
  "peerDependencies": {
    "@zod/core": "*"
  }
}

Since package managers generally won't install your own peerDependencies, you'll need to add @zod/core to your devDependencies as well. As new versions of @zod/core are released, you can update your devDependencies to match the latest version. This is important for testing and development purposes.

{
  "peerDependencies": {
    "@zod/core": "*"
  },
  "devDependencies": {
    "@zod/core": "^0.1.0"
  }
}
  1. Allow for new schemas and checks to be added

If you are switching over $ZodTypes or $ZodChecks, account for the possibility that new types or checks will be added in future minor versions. The addition of new APIs is not considered a breaking change.

Moreover, some users may define their own custom subclasses of $ZodType. This is less likely

Consider printing an informative warning to the console and providing some reasonable fallback behavior if you encounter an unknown schema type.

On this page