Zod 4
https://v4.zod.dev/ecosystem
I am aware and will be seeing how we can migrate in an easy way :)
Probably will require:
- Add a codegen to migrate from
.openapi()to.meta(). - Delete some schema generation internals
Some current issues I'm waiting on:
https://github.com/colinhacks/zod/issues/4089 https://github.com/colinhacks/zod/issues/4094 https://github.com/colinhacks/zod/issues/4134
PRs:
https://github.com/colinhacks/zod/pull/4091 https://github.com/colinhacks/zod/pull/4090
Please be patient, as this may take a little time to work through with Colin to make sure we get the result which will lead to the least changes for you.
If Zod 4's json schema generation doesn't reach what I deem an acceptable level of parity with this library, I'll keep .openapi() around for a little while longer and keep chipping away at adding features to the main zod lib.
I think it would be great to see you guys adopt .meta() BUT I have nothing against the prototype/declare module approach in general. I think it's a totally valid way to extend Zod. It's very possible your existing users would prefer a seamless migration path to use Zod 4 without a big API change.
Very curious to hear any thoughts you have on v4 and any issues you encounter migrating your libraries! ❤️
PS left a comment here: https://github.com/colinhacks/zod/pull/4090
Linking to my PR so people can find this tracking issue from there: https://github.com/colinhacks/zod/pull/4074
All PRs & issues have been closed on my end! Keep them coming!
@colinhacks
I think it would be great to see you guys adopt .meta() BUT I have nothing against the prototype/declare module approach in general. I think it's a totally valid way to extend Zod. It's very possible your existing users would prefer a seamless migration path to use Zod 4 without a big API change.
I wouldn't encourage it. It seem to break integrations with other libraries, e.g., https://github.com/samchungy/zod-openapi/issues/441
Thanks for your patience everyone, I should have something in a decent state by the start of next week.
@samchungy I migrated earlier this week most parsers to accept both v3 and v4/core types (in fact I migrated most of the lib). I am not sure where/how you chose to implement support for v4, if it helps more than it adds overhead to you I would be happy to share.
@ct-verio Can you please publish your changes on a fork? I'd love to try them out.
@ct-verio Can you please publish your changes on a fork? I'd love to try them out.
Yes, I'll fork it, need few hours though. It's not 100% and I could not figure out yet how to add the openapi() implementation to the v4 prototypes (given the core does not expose classes, rather constructor functions) - TS/JS is not my forte :) I was able to pass 100% of the tests for v3, but I can't run tests for v4 involving the openapi() extension yet
Few comments:
- there is no more native enum in core - enum is using record type and for literal string arrays the key is the same as the value
- check parameters are no longer directly present in the high-level type, I had to iterate through the checks collection and extract what was relevant.
- to check if an object is of a certain type I used the "traits" array
- to extract the actual name of the zod type I used ._zod.constr.name
- I suffixed some of the v3 methods with V3, and left the original name for cross-compatible or v4-related methods
...one hour later :) I pushed my changes to zod-v4 branch on this fork: zod-openapi I noticed there's a breaking change since yesterday - I cannot run pnpm i or test anymore, it seems it has some issue with skuba/jest installation but I did not have enough time to dig. If I remove pnpm-workspace.yaml and install @types/jest I can build it though.
Does the new version use meta()?
Does the new version use
meta()?
if you are asking about my fork, it does not - I tried to minimize the changes to the existing code, even though I tried initially to get rid of the extension. You could use meta as-is but then the existing merging logic down the chain will break, or anchor zodOpenApi / openapi inside metadata - I thought the decision should be left to @samchungy & others who have a more informed say. This one does not even have the extension logic working yet, only the parsers are updated.
Does the new version use
meta()?
In my current iteration, yes. Which is unfortunately a breaking change but means we hopefully can remove the side effects that this library currently introduces via the extend import/function call
I'm trying to also provide a code-mod to do most of the migration for all of you.
Does the new version use
meta()?In my current iteration, yes. Which is unfortunately a breaking change but means we hopefully can remove the side effects that this library currently introduces via the extend import/function call
I'm trying to also provide a code-mod to do most of the migration for all of you.
That's awesome. That's preferable
@samchungy Is there any timeline for adding support for zod/v4 in zod-openapi? I just wanted to make sure before updating to Zod v4 to avoid any breaking changes.
@samchungy Is there any timeline for adding support for
zod/v4inzod-openapi? I just wanted to make sure before updating to Zod v4 to avoid any breaking changes.
I was hoping to have it mostly complete by this week, but I've been running into some integration issues. I've been mostly linking the issues blocking/slowing me down in the OP.
Mind you, I don't have the same ability as Collin to work on this full-time, so it's mostly me plugging away at it after work or during my own time.
Would anybody be angry if I dropped support for < OpenAPI 3.1.0?
It's proving challenging to support both at the moment.
Not at all
Would anybody be angry if I dropped support for < OpenAPI 3.1.0?
Nope
Would anybody be angry if I dropped support for < OpenAPI 3.1.0?
It's proving challenging to support both at the moment.
You could even only support >= 3.1.1 https://github.com/OAI/OpenAPI-Specification/releases/tag/3.1.1
Okay hey everyone, sorry for the wait. This ended up being essentially a complete rewrite.
I expect to do a full release sometime this week. I'm yet to write a codegen to help people migrate but if you want to give the new version a shot It's available on npm via zod-openapi@beta
Please note: The CHANGELOG is fairly substantial but you should be able to migrate following this guide: https://github.com/samchungy/zod-openapi/blob/v4-stash/docs/v5.md
@samchungy Thank you so much! I am using it immediately for my work project and it is working well✨
This is a minor request, but it might be more tree-shaking friendly to import with wildcard.
import * as z from 'zod/v4';
As an example, tree-shaking the following code with tsup resulted in a significant change in file size.
import * as z from 'zod/v4';
// import { z } from 'zod/v4';
const schema = z.string();
console.log(schema);
- wildcard: 81.18 KB
- no wildcard: 383.91 KB
This may not be very important since the product is basically used on the server side, but we would appreciate your consideration.
Can I suggest to add a utility that allows to type guard definitions?
Example:
This:
const RepositoryModelZodSchema = z.object({
url: z.url().meta({
description: 'URL to the repository hosting the source code',
example: 'https://github.com/pskill9/hn-server',
}),
});
Would become:
const RepositoryModelZodSchema = z.object({
url: z.url().meta(zodOpenApiMeta({
description: 'URL to the repository hosting the source code',
example: 'https://github.com/pskill9/hn-server',
})),
});
where zodOpenApiMeta is simply a function that tests the types:
const zodOpenApiMeta = (meta: ZodOpenApiMeta) => {
return meta;
}
This would help prevent accidentally introducing unknown properties.
@samchungy does https://github.com/samchungy/fastify-zod-openapi need to be updated to be compatible with v5?
@punkpeye
Can I suggest to add a utility that allows to type guard definitions?
The description and ~~example~~ are already defined in the GlobalMeta interface of zod/v4.
So it should be type safe without doing anything, but won't it work?
→PS: The example type seems to have been removed in the latest release(v3.25.68).
And, zod-openapi's own properties are defined by extending GlobalMeta interface, so you can resolve the type by either importing zod-openapi or including d.ts containing /// <reference types="zod-openapi" /> in tsconfig to include it, of these will resolve the type.
https://github.com/samchungy/zod-openapi/blob/beta/src/types.ts#L11
@punkpeye
does https://github.com/samchungy/fastify-zod-openapi need to be updated to be compatible with v5?
A pull request has already been created. The status is still draft. https://github.com/samchungy/fastify-zod-openapi/pull/263
Can I suggest to add a utility that allows to type guard definitions?
Example:
This:
const RepositoryModelZodSchema = z.object({ url: z.url().meta({ description: 'URL to the repository hosting the source code', example: 'https://github.com/pskill9/hn-server', }), }); Would become:
const RepositoryModelZodSchema = z.object({ url: z.url().meta(zodOpenApiMeta({ description: 'URL to the repository hosting the source code', example: 'https://github.com/pskill9/hn-server', })), }); where
zodOpenApiMetais simply a function that tests the types:const zodOpenApiMeta = (meta: ZodOpenApiMeta) => { return meta; } This would help prevent accidentally introducing unknown properties.
I could potentially export a type like ZodOpenApiMeta or maybe something shorter like Meta so you can go z.string().meta({ description: 'foo' } satisfies ZodOpenApiMeta); but what mdoi said is correct we do already extend the global types.
... but what mdoi said is correct we do already extend the global types.
Please don't.
It is truly one of the worst anti-patterns. Importing a package should not under any circumstances magically change zod types across the entire project.
Just provide a utility or ZodOpenApiMeta that users themselves need to validate against.
... but what mdoi said is correct we do already extend the global types.
Please don't.
It is truly one of the worst anti-patterns. Importing a package should not under any circumstances magically change zod types across the entire project.
Just provide a utility or
ZodOpenApiMetathat users themselves need to validate against.
I think you may be misinterpreting this. We're not performing any monkey patching this time around with our import. It's purely a TypeScript type import so you can choose to keep this as a dev dependency.
It purely extends the .meta() TypeScript types with optional types to give you better type hints and means the dependency on this library can be removed at compile time.
Requiring a runtime function is both ugly and unnecessary. We can avoid this by using a satisfies type.