> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/colinhacks/zod/llms.txt
> Use this file to discover all available pages before exploring further.

# Guide for Library Authors

> Guidelines and best practices for library authors integrating with Zod

This page is primarily intended for consumption by **library authors** who are building tooling on top of Zod.

<Note>
  **Update — July 10th, 2025**

  Zod `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`:

  ```json theme={null}
  // 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.
</Note>

## 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](https://standardschema.dev/). It's a shared interface implemented by most popular validation libraries in the TypeScript ecosystem (see the [full list](https://standardschema.dev/#what-schema-libraries-implement-the-spec)), 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".

```json theme={null}
// 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.

```json theme={null}
// 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](https://github.com/colinhacks/zod/issues/4371) writeup for full context on this versioning approach.

```ts theme={null}
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 `zod@3.24` and `zod@3.25`; 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.

```ts theme={null}
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.

```ts theme={null}
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.

```ts theme={null}
// 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.

```ts theme={null}
// 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](/packages/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:

```ts theme={null}
import * as z4 from "zod/v4/core";

function inferSchema<T>(schema: z4.$ZodType<T>) {
  return schema;
}
```

<Warning>
  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`.

  ```ts theme={null}
  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`.
</Warning>

Instead, your generic parameter should extend the core Zod schema interface:

```ts theme={null}
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:

```ts theme={null}
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:

```ts theme={null}
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.

```ts theme={null}
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:

```json theme={null}
// 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:

```md theme={null}
## 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:**

```ts theme={null}
import * as z4 from "zod/v4/core";

function getSchemaType<T extends z4.$ZodType>(schema: T) {
  return schema._zod.def.type;
}
```

❌ **Bad:**

```ts theme={null}
// 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:

* [tRPC](https://github.com/trpc/trpc) - Type-safe API framework
* [React Hook Form](https://github.com/react-hook-form/resolvers) - Form validation
* [Fastify Type Provider](https://github.com/turkerdev/fastify-type-provider-zod) - Request/response validation

Studying how these libraries implemented Zod 4 support can provide valuable insights for your own implementation.
