Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Author here. I wrote a fairly detailed writeup here[0] for those who are interested in the reasons for this approach.

Ultimately you're right that npm doesn't work well to manage the situation Zod finds itself in. But Zod is subject to a bunch of constraints that virtually no other libraries are subject to. There are dozens or hundreds of libraries that directly import interfaces/classes from "zod" and use them in their own public-facing API.

Since these libraries are directly coupled to Zod, they would need to publish a new major version whenever Zod does. That's ultimately reasonable in isolation, but in Zod's case it would trigger a "version avalanche" would just be painful for everyone involved. Selfishly, I suspect it would result in a huge swath of the ecosystem pinning on v3 forever.

The approach I ended up using is analogous to what Golang does. In essence a given package never publishes new breaking versions: they just add a new subpath when a new breaking release is made. In the TypeScript ecosystem, this means libraries can configure a single peer dependency on zod@^3.25.0 and support both versions simultaneously by importing what they need from "zod/v3" and "zod/v4". It provides a nice opt-in incremental upgrade path for end-users of Zod too.

[0] https://github.com/colinhacks/zod/issues/4371



Are there any plans of doing an actual v4 release and making zod/v4 the default there? Perhaps make simulatenous releases of zod v3 containing a /v4 path and a zod v4 containing a /v3 path? Then converge on just zod v4 with no /v3 from 4.1 onwards.


Yep, at some indeterminate point when I gauge that there's sufficient support for Zod 4 in the ecosystem, I'll publish `zod@4.0.0` to npm. This is detailed in the writeup[0]

[0] https://github.com/colinhacks/zod/issues/4371


Is there any advantage to this approach over publishing a separate "zod4" package? That would be just as opt-in and incremental, not bloat download sizes forever, and make it easier to not accidentally import the wrong thing.


Ecosystem libraries would need to switch from a single peer dependency on Zod to two optional peer dependencies. Despite "optional peer dependencies" technicall being a thing, its functionally impossible for a library to determine which of the two packages your user is actually bringing to the table.

Let's say a library is trying to implement an `acceptSchema` function that can accepts `Zod3Type | Zod4Type`. For starters: those two interfaces won't both be available if Zod 3 and Zod 4 are in separate packages. So that's already a non-starter. And differentiating them at runtime requires knowing which package is installed, which is impossible in the general case (mostly because frontend bundlers generally have no affordance for optional peer dependencies).

I describe this in more detail here: https://x.com/colinhacks/status/1922101292849410256


Thanks a lot for zod and really looking forward to trying out the new version! Also as a primary Go developer this issue just has been really interesting to read about. Go avoids this issue by just not having peer dependencies and relying on the compiler to not bundle unused code - or just live with huge binaries when it doesn't work out :-)

I am still curious about the `zod4` idea though. Any thoughts on adding an `__api_version` type of property to distinguish on to all of zod's public types? Perhaps it's prohibitive code-size wise but wondering if it's possible somehow. Then downstream libraries could differentiate by doing the property check.

Just wanted to share the idea but the current state seems ok too.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: