Skip to main content
This page is primarily intended for consumption by library authors who are building tooling on top of Zod.
Update — July 10th, 2025Zod 4.0.0 has been released on npm. This completes the incremental rollout process. To add support, bump your peer dependency to include zod@^4.0.0:
// package.json
{
  "peerDependencies": {
    "zod": "^3.25.0 || ^4.0.0"
  }
}
If you’d already implemented Zod 4 support according to the best practices described below (e.g. using the "zod/v4/core" subpath), then no other code changes should be necessary. This should not require a major version bump in your library.

Do I need to depend on Zod?

First things first, make sure you need to depend on Zod at all. If you’re building a library that accepts user-defined schemas to perform black-box validation, you may not need to integrate with Zod specifically. Instead look into Standard Schema. It’s a shared interface implemented by most popular validation libraries in the TypeScript ecosystem (see the full list), including Zod. This spec works great if you accept user-defined schemas and treat them like “black box” validators. Given any compliant library, you can extract inferred input/output types, validate inputs, and get back a standardized error. If you need Zod specific functionality, read on.

How to configure peer dependencies?

Any library built on top of Zod should include "zod" in "peerDependencies". This lets your users “bring their own Zod”.
// package.json
{
  "peerDependencies": {
    "zod": "^3.25.0 || ^4.0.0"
  }
}
During development, you need to meet your own peer dependency requirement, to do so, add "zod" to your "devDependencies" as well.
// package.json
{
  "peerDependencies": {
    "zod": "^3.25.0 || ^4.0.0"
  },
  "devDependencies": {
    "zod": "^4.0.0"
  }
}

How to support Zod 4?

To support Zod 4, update the minimum version for your "zod" peer dependency to ^3.25.0 || ^4.0.0. Starting with v3.25.0, the Zod 4 core package is available at the "zod/v4/core" subpath. Read the Versioning in Zod 4 writeup for full context on this versioning approach.
import * as z4 from "zod/v4/core";
Import from these subpaths only. Think of them like “permalinks” to their respective Zod versions. These will remain available forever.
  • "zod/v3" for Zod 3 ✅
  • "zod/v4/core" for the Zod 4 Core package ✅
You generally shouldn’t be importing from any other paths:
  • "zod" — ❌ In 3.x releases, this exports Zod 3. In 4.x releases, this will export Zod 4. Use the permalinks instead.
  • "zod/v4" and "zod/v4/mini" — ❌ These subpaths are the homes of Zod 4 Classic and Mini, respectively. If you want your library to work with both Zod and Zod Mini, you should build against the base classes defined in "zod/v4/core".

Do I need to publish a new major version?

No, you should not need to publish a new major version of your library to support Zod 4 (unless you are dropping support for Zod 3, which isn’t recommended). You will need to bump your peer dependency to ^3.25.0, thus your users will need to npm upgrade zod. But there were no breaking changes made to Zod 3 between [email protected] and [email protected]; in fact, there were no code changes whatsoever. As no code changes will be required on the part of your users, this does not constitute a breaking change.

How to support Zod 3 and Zod 4 simultaneously?

Starting in v3.25.0, the package contains copies of both Zod 3 and Zod 4 at their respective subpaths. This makes it easy to support both versions simultaneously.
import * as z3 from "zod/v3";
import * as z4 from "zod/v4/core";

type Schema = z3.ZodTypeAny | z4.$ZodType;

function acceptUserSchema(schema: z3.ZodTypeAny | z4.$ZodType) {
  // ...
}
To differentiate between Zod 3 and Zod 4 schemas at runtime, check for the "_zod" property. This property is only defined on Zod 4 schemas.
import type * as z3 from "zod/v3";
import type * as z4 from "zod/v4/core";

declare const schema: z3.ZodTypeAny | z4.$ZodType;

if ("_zod" in schema) {
  schema._zod.def; // Zod 4 schema
} else {
  schema._def; // Zod 3 schema
}

How to support Zod and Zod Mini simultaneously?

Your library code should only import from "zod/v4/core". This sub-package defines the interfaces, classes, and utilities that are shared between Zod and Zod Mini.
// library code
import * as z4 from "zod/v4/core";

export function acceptObjectSchema<T extends z4.$ZodObject>(schema: T) {
  // parse data
  z4.parse(schema, { /* somedata */ });
  // inspect internals
  schema._zod.def.shape;
}
By building against the shared base interfaces, you can reliably support both sub-packages simultaneously. This function can accept both Zod and Zod Mini schemas.
// user code
import { acceptObjectSchema } from "your-library";

// Zod 4
import * as z from "zod";
acceptObjectSchema(z.object({ name: z.string() }));

// Zod 4 Mini
import * as zm from "zod/mini";
acceptObjectSchema(zm.object({ name: zm.string() }));
Refer to the Zod Core page for more information on the contents of the core sub-library.

How to accept user-defined schemas?

Accepting user-defined schemas is a fundamental operation for any library built on Zod. This section outlines the best practices for doing so. When starting out, it may be tempting to write a function that accepts a Zod schema like this:
import * as z4 from "zod/v4/core";

function inferSchema<T>(schema: z4.$ZodType<T>) {
  return schema;
}
This approach is incorrect, and limits TypeScript’s ability to properly infer the argument. No matter what you pass in, the type of schema will be an instance of $ZodType.
inferSchema(z.string());
// => $ZodType<string>
This approach loses type information, namely which subclass the input actually is (in this case, ZodString). That means you can’t call any string-specific methods like .min() on the result of inferSchema.
Instead, your generic parameter should extend the core Zod schema interface:
function inferSchema<T extends z4.$ZodType>(schema: T) {
  return schema;
}

inferSchema(z.string());
// => ZodString ✅

Constraining to specific schema types

To constrain the input schema to a specific subclass:
import * as z4 from "zod/v4/core";

// only accepts object schemas
function acceptObjectSchema<T extends z4.$ZodObject>(schema: T) {
  return schema;
}
To constrain the inferred output type of the input schema:
import * as z4 from "zod/v4/core";

// only accepts string schemas
function acceptStringSchema<T extends z4.$ZodType<string>>(schema: T) {
  return schema;
}

acceptStringSchema(z.string()); // ✅

acceptStringSchema(z.number());
// ❌ The types of '_zod.output' are incompatible between these types.
// Type 'number' is not assignable to type 'string'

Parsing data with schemas

To parse data with the schema, use the top-level z4.parse/z4.safeParse/z4.parseAsync/z4.safeParseAsync functions. The z4.$ZodType subclass has no methods on it. The usual parsing methods are implemented by Zod and Zod Mini, but are not available in Zod Core.
function parseData<T extends z4.$ZodType>(data: unknown, schema: T): z4.output<T> {
  return z4.parse(schema, data);
}

parseData("sup", z.string());
// => string

Best practices

Use semantic versioning carefully

When adding Zod 4 support:
  • Minor version bump: If you’re adding Zod 4 support while maintaining Zod 3 compatibility
  • Major version bump: Only if you’re dropping Zod 3 support entirely

Test against multiple Zod versions

Set up your test suite to run against both Zod 3 and Zod 4 to ensure compatibility:
// package.json
{
  "scripts": {
    "test:zod3": "npm install zod@^3.25.0 && npm test",
    "test:zod4": "npm install zod@^4.0.0 && npm test",
    "test:all": "npm run test:zod3 && npm run test:zod4"
  }
}

Document Zod version support

Be clear in your README about which versions of Zod your library supports:
## Requirements

- Zod ^3.25.0 or ^4.0.0

This library supports both Zod 3 and Zod 4. For Zod 3 users, version 3.25.0 or higher is required.

Avoid relying on internal APIs

Stick to the public API surface as much as possible. Internal implementation details may change between versions. Good:
import * as z4 from "zod/v4/core";

function getSchemaType<T extends z4.$ZodType>(schema: T) {
  return schema._zod.def.type;
}
Bad:
// Don't rely on undocumented internal properties
function getInternalCache(schema: any) {
  return schema._cachedPath; // may not exist in all versions
}

Examples from the ecosystem

Here are some examples of libraries that have successfully implemented Zod 4 support: Studying how these libraries implemented Zod 4 support can provide valuable insights for your own implementation.

Build docs developers (and LLMs) love