Capítulo 48 de 61

Chapter 48: For Library Authors — Peer Dependencies & Subpaths

Core Idea

Libraries built on Zod should depend on it as a peer dependency, import only from the stable "zod/v4/core" subpath (not "zod", "zod/v4", or "zod/v4/mini"), and — if genuinely accepting arbitrary user-defined schemas rather than needing Zod-specific behavior — consider Standard Schema instead of a hard Zod dependency at all.

Key Concepts

  • Standard Schema first: if you just need to validate against user-supplied schemas as black boxes (extract input/output types, validate, get a normalized error), Standard Schema is a shared interface most validation libraries implement (including Zod) — it avoids a Zod-specific dependency entirely.
  • Peer dependency, not a direct dependency: list "zod": "^4.0.0" under peerDependencies (and duplicate it under devDependencies for local development) so consumers "bring their own Zod" version.
  • Import only "zod/v4/core": this is the permanent, version-stable subpath shared by both Zod Classic and Zod Mini — it defines the $-prefixed base classes both implementations extend. Avoid "zod" (its meaning shifts between major versions), and avoid "zod/v4"/"zod/v4/mini" directly, since code built against either one won't work with the other.
  • Supporting Zod 3 and Zod 4 together: widen the peer range to "^3.25.0 || ^4.0.0" (the "zod/v4" subpath exists starting at 3.25.0) and import "zod/v3" alongside "zod/v4/core"; distinguish schema versions at runtime by checking for the "_zod" property (present only on Zod 4 schemas). New libraries (or new major versions) should target Zod 4 only — Zod 3 is functionally end-of-life, security/bug fixes only, no new features.
  • Supporting Zod and Zod Mini together: build exclusively against "zod/v4/core" types/functions; since both Classic and Mini extend the same core classes, a function written against the core interface accepts schemas from either package transparently.

Code Examples

// package.json
{
  "peerDependencies": { "zod": "^4.0.0" },
  "devDependencies": { "zod": "^4.0.0" }
}
// correct: import the stable core subpath
import * as z4 from "zod/v4/core";

export function acceptObjectSchema<T extends z4.$ZodObject>(schema: T) {
  z4.parse(schema, { /* data */ });
  schema._zod.def.shape;
}

// works transparently with both packages
import * as z from "zod";
acceptObjectSchema(z.object({ name: z.string() }));

import * as zm from "zod/mini";
acceptObjectSchema(zm.object({ name: zm.string() }));

// distinguishing Zod 3 vs Zod 4 schemas at runtime
if ("_zod" in schema) {
  schema._zod.def; // Zod 4
} else {
  schema._def; // Zod 3
}
  • What it demonstrates: the peer-dependency setup, building library code against the shared core so it works with both Zod and Zod Mini, and runtime version detection for dual Zod 3/4 support.

Anti-patterns

  • Importing from "zod/v4" or "zod/v4/mini" directly in library code: locks the library to one flavor and breaks for users of the other.
  • Writing function f<T>(schema: z4.$ZodType<T>): this loses the specific subclass information — TypeScript can't infer which schema type was actually passed, so callers lose access to type-specific methods on the result.

Key Takeaways

  1. Before adding any Zod dependency, check whether Standard Schema alone covers the need — it avoids the whole versioning/subpath problem.
  2. "zod/v4/core" is the one import path guaranteed to keep working across future major versions and both Classic/Mini flavors — treat it as the only sanctioned import for library internals.
  3. Dual Zod 3/4 support is a minor-version-compatible peer range widening, not a breaking change, as long as your code only touches APIs common to both.

Connects To

  • For Library Authors — Accepting User-Defined Schemas: the generic-typing pattern for functions that take a schema parameter.
  • Zod Core — Schemas & Internals: what actually lives in the "zod/v4/core" package this chapter tells you to depend on.