Skip to content

Commit abc13de

Browse files
committed
Add major version section
1 parent 99e2329 commit abc13de

File tree

3 files changed

+79
-31
lines changed

3 files changed

+79
-31
lines changed

packages/docs/content/library-authors.mdx

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,27 @@ To support Zod 4, update the minimum version for your `"zod"` peer dependency to
6161
}
6262
```
6363

64-
Starting with `v3.25.0`, Zod 4 is available at a `/v4` subpath.
64+
Starting with `v3.25.0`, the Zod 4 core package is available at a `/v4/core` subpath. Read the [Versioning in Zod 4](https://github.com/colinhacks/zod/issues/4371) writeup for full context on this versioning approach.
6565

6666
```ts
6767
import * as z4 from "zod/v4/core";
6868
```
6969

70-
Library code should *not* import from the package root (`"zod"`)! Instead, import from the version-specific subpaths: `"zod/v3"` and `"zod/v4/core"`. This way, your code is future-proofed against major version bumps down the line.
70+
Import from these subpaths only. Think of them like "permalinks" to their respective Zod versions. These will remain available forever.
71+
72+
- `"zod/v3"` for Zod 3 ✅
73+
- `"zod/v4/core"` for the Zod 4 Core package ✅
74+
75+
Do not import from these subpaths:
76+
77+
- `"zod"` — ❌ In 3.x releases, this exports Zod 3. In a [future 4.x release](https://github.com/colinhacks/zod/issues/4371), this will export Zod 4. Use the permalinks instead.
78+
- `"zod/v4"`— ❌ This subpath is the home of Zod 4 "Classic". If you reference classes from the standard module, your library will not work with Zod Mini. This is extremely discouraged. Use `"zod/v4/core"` instead, which exports the `$`-prefixed subclasses that are extended by Zod Classic and Zod Mini. The internals of the classic & mini subclasses are identical; they only differ in which helper methods they implement.
79+
80+
## Do I need to publish a new major version?
81+
82+
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).
83+
84+
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 code changes will be required on the part of your users, I do not believe this constitutes a breaking change. I recommend against publishing a new major version.
7185

7286
## How to support Zod 3 and Zod 4 simultaneously?
7387

@@ -88,7 +102,7 @@ To differentiate between Zod 3 and Zod 4 schemas at runtime, check for the `"_zo
88102

89103
```ts
90104
import type * as z3 from "zod/v3";
91-
import type * as v4 from "zod/v4/core";
105+
import type * as z4 from "zod/v4/core";
92106

93107
declare const schema: z3.ZodTypeAny | v4.$ZodType;
94108

@@ -101,15 +115,15 @@ if ("_zod" in schema) {
101115

102116
## How to support Zod and Zod Mini simultaneously?
103117

104-
Your library code should only import from `zod/v4/core`. This sub-package defines the interfaces, classes, and utilities that are shared between `zod/v4` and `zod/v4-mini`.
118+
Your library code should only import from `"zod/v4/core"`. This sub-package defines the interfaces, classes, and utilities that are shared between `zod/v4` and `zod/v4-mini`.
105119

106120
```ts
107121
// library code
108-
import * as z from "zod/v4/core";
122+
import * as z4 from "zod/v4/core";
109123

110-
export function acceptObjectSchema<T extends z.$ZodObject>(schema: T){
124+
export function acceptObjectSchema<T extends z4.$ZodObject>(schema: T){
111125
// parse data
112-
z.parse(schema, { /* somedata */});
126+
z4.parse(schema, { /* somedata */});
113127
// inspect internals
114128
schema._zod.def.shape;
115129
}
@@ -165,39 +179,39 @@ Accepting user-defined schemas is the a fundamental operation for any library bu
165179
When starting out, it may be tempting to write a function that accepts a Zod schema like this:
166180

167181
```ts
168-
import * as z from "zod/v4";
182+
import * as z4 from "zod/v4/core";
169183

170-
function inferSchema<T>(schema: z.core.$ZodType<T>) {
184+
function inferSchema<T>(schema: z4.$ZodType<T>) {
171185
return schema;
172186
}
173187
```
174188

175-
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`.
189+
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`.
176190

177191
```ts
178192
inferSchema(z.string());
179-
// => z.core.$ZodType<string>
193+
// => $ZodType<string>
180194
```
181195

182196
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:
183197

184198
```ts
185-
function inferSchema<T extends z.core.$ZodType>(schema: T) {
199+
function inferSchema<T extends z4.$ZodType>(schema: T) {
186200
return schema;
187201
}
188202

189203
inferSchema(z.string());
190-
// => ZodString
204+
// => ZodString
191205
```
192206

193207
To constrain the input schema to a specific subclass:
194208

195209
```ts
196210

197-
import * as z from "zod/v4";
211+
import * as z4 from "zod/v4/core";
198212

199213
// only accepts object schemas
200-
function inferSchema<T>(schema: z.core.$ZodObject) {
214+
function inferSchema<T>(schema: z4.$ZodObject) {
201215
return schema;
202216
}
203217
```
@@ -206,10 +220,10 @@ To constrain the inferred output type of the input schema:
206220

207221
```ts
208222

209-
import * as z from "zod/v4";
223+
import * as z4 from "zod/v4/core";
210224

211225
// only accepts string schemas
212-
function inferSchema<T extends z.core.$ZodType<string>>(schema: T) {
226+
function inferSchema<T extends z4.$ZodType<string>>(schema: T) {
213227
return schema;
214228
}
215229

@@ -220,10 +234,10 @@ inferSchema(z.number());
220234
// // Type 'number' is not assignable to type 'string'
221235
```
222236

223-
To parse data with the schema, use the top-level `z.parse`/`z.safeParse`/`z.parseAsync`/`z.safeParseAsync` functions. The `z.core.$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.
237+
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.
224238

225239
```ts
226-
function parseData<T extends z.core.$ZodType>(data: unknown, schema: T): z.output<T> {
240+
function parseData<T extends z4.$ZodType>(data: unknown, schema: T): z4.output<T> {
227241
return z.parse(schema, data);
228242
}
229243

packages/zod/src/v4/core/function.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,15 @@ export type $InferInnerFunctionTypeAsync<Args extends $ZodFunctionIn, Returns ex
3333
...args: null extends Args ? never[] : NonNullable<Args>["_zod"]["output"]
3434
) => null extends Returns ? unknown : util.MaybeAsync<NonNullable<Returns>["_zod"]["input"]>;
3535

36-
export type $InferOuterFunctionType<Args extends $ZodFunctionIn, Returns extends $ZodFunctionOut> = (
37-
...args: null extends Args ? never[] : NonNullable<Args>["_zod"]["input"]
38-
) => null extends Returns ? unknown : NonNullable<Returns>["_zod"]["output"];
36+
// export type $InferOuterFunctionType<Args extends $ZodFunctionIn, Returns extends $ZodFunctionOut> = (
37+
// ...args: null extends Args ? never[] : NonNullable<Args>["_zod"]["input"]
38+
// ) => null extends Returns ? unknown : NonNullable<Returns>["_zod"]["output"];
39+
export interface $InferOuterFunctionType<Args extends $ZodFunctionIn, Returns extends $ZodFunctionOut> {
40+
// biome-ignore lint:
41+
(
42+
...args: null extends Args ? [] : NonNullable<Args>["_zod"]["input"]
43+
): null extends Returns ? unknown : NonNullable<Returns>["_zod"]["output"];
44+
}
3945

4046
export type $InferOuterFunctionTypeAsync<Args extends $ZodFunctionIn, Returns extends $ZodFunctionOut> = (
4147
...args: null extends Args ? never[] : NonNullable<Args>["_zod"]["input"]
@@ -65,7 +71,10 @@ export class $ZodFunction<
6571

6672
implement<F extends $InferInnerFunctionType<Args, Returns>>(
6773
func: F
68-
): F extends this["_output"] ? F : this["_output"] {
74+
): // allow for return type inference
75+
ReturnType<F> extends ReturnType<this["_output"]>
76+
? (...args: Parameters<this["_output"]>) => ReturnType<F>
77+
: this["_output"] {
6978
if (typeof func !== "function") {
7079
throw new Error("implement() must be called with a function");
7180
}

play.ts

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,37 @@
11
import { z } from "zod/v4";
22

3-
z;
4-
const a = z
5-
.string()
6-
.transform((val) => val.length)
7-
.default(5);
3+
const a = z.object({
4+
name: z.string(),
5+
age: z.number().int().positive(),
6+
});
87

9-
console.dir(z.toJSONSchema(a, { io: "input" }), { depth: null });
10-
z.formatError;
8+
console.dir(
9+
z.toJSONSchema(a, {
10+
override(ctx) {
11+
const schema = ctx.zodSchema;
12+
if (schema instanceof z.core.$ZodObject && schema._zod.def.catchall === undefined) {
13+
delete ctx.jsonSchema.additionalProperties;
14+
}
15+
},
16+
}),
17+
{ depth: null }
18+
);
1119

12-
z.jwt({ alg: "Edscaasdf" });
20+
const arg = z.string().safeParse("hello"); // should not throw
21+
z.treeifyError(arg.error!);
22+
23+
// import { z } from "zod/v4";
24+
25+
const output = z.string();
26+
27+
const itWorks = z.function({ input: [z.string()] }).implement(output.parse);
28+
const itWorks2 = z.function({ input: [z.string()] }).implement((args) => output.parse(args));
29+
const itWorks3 = z.function({ input: [z.string().default("")] }).implement(output.parse);
30+
const itWorks4 = z.function({ input: [z.string()] }).implement((args) => output.parse(args));
31+
const nope = z.function({ input: [z.string().default("")] }).implement((args) => output.parse(args));
32+
33+
type ItWorks = ReturnType<typeof itWorks>;
34+
type ItWorks2 = ReturnType<typeof itWorks2>;
35+
type ItWorks3 = ReturnType<typeof itWorks3>;
36+
type ItWorks4 = ReturnType<typeof itWorks4>;
37+
type Nope = ReturnType<typeof nope>; //unknown

0 commit comments

Comments
 (0)