5/19/2025 at 6:32:43 PM
Author here, AMA!Regarding the versioning: I wrote a fairly detailed writeup here[0] for those who are interested in the reasons for this approach.
Ultimately npm is not designed to handle the situation Zod finds itself in. Zod is subject to a bunch of constraints that virtually no other libraries are subject to. Namely, the 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.
by colinmcd
5/19/2025 at 8:39:08 PM
Many thanks for your work! Definitely looking forward to the upgrade - especially the performance-boosts with regards to tsc will be very welcome in our relatively large code-base, and the changes to discriminated unions will probably help us big time in a very specific scenario where so far they fell short.That being said, I'm fully understanding of the reasons for the somewhat odd versioning given your special situation, but still, I'd wish there would be a 4.0.0-package for folks like us who simply don't need to worry or bother about zod-version-clashes in transitive dependencies bacause those don't exist (or at least I think so; npm ls zod only returns our own dependency of zod). If I understood correctly, we'll need to adapt the import to "zod/v4", which will be an incredibly noisy change and will probably cause quite a few headaches when IDEs auto-import from 'zod' and such, which we then need to catch with linting-rules.
But that's probably a small gripe for a what sounds overall like a very promising upgrade - many thanks for your work once again!
by Shacklz
5/19/2025 at 9:20:56 PM
+1. We use Zod only for internal validation and would love a standalone release for v4 (or another package like zod4 or @zod/zod). I'm sure many greenfield projects would prefer that, too.by e1g
5/20/2025 at 9:05:29 AM
> 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.This is extremely sensible. And it means you can provide security updates for older versions, just all in the same codebase's releases.
by robertlagrant
5/19/2025 at 8:40:58 PM
Sorry if it's mentioned in the article but I'm on mobile.Is fixing .optional() in TS[0] part of the 9/10 top-issues fixed? This has been my biggest pain point with Zod... but still Zod is so good I still choose to just deal with it :) Thanks for an amazing part of the ecosystem.
by kaoD
5/20/2025 at 7:26:50 AM
Yes! Zod now differentiates between `z.string().optional()` and `z.union([z.string(), z.undefined()])` (as in TypeScript itself). Details: https://x.com/colinhacks/status/1919291504587137496by colinmcd
5/19/2025 at 6:57:17 PM
First thank you for the hard work, many of my local hacks will go away with the new features!As a convenience and mostly avoid typos in form names I use my own version of https://github.com/raflymln/zod-key-parser. I've been surprised something like this hasn't been implemented directly in the library.
Curious if you think this is out of scope for Zod or just something you haven't gotten around to implement?
(Here are discussions around it: https://github.com/colinhacks/zod/discussions/2134)
by elbajo
5/19/2025 at 7:10:03 PM
Some kind of affordance for FormData/URLSearchParams-style structures is definitely in scope. It was a late cut. Ultimately HTML inputs/forms are an implicit type system unto itself—certainly HTML has a very different notion of "optional" than TypeScript. So my approach to this would likely involve another sub-library ("zod/v4-form").I like your library! Put in a PR to add it to the ecosystem page :)
by colinmcd
5/19/2025 at 7:55:08 PM
Great to hear this is something you are considering!To be clear: this isn't my library. This is just something I found while trying to solve the FormData issue. Props go to https://github.com/raflymln who created it.
by elbajo
5/20/2025 at 12:55:19 AM
Just would like to say I really appreciate your consideration of releasing breaking changes. I know not all libraries can employ your methodology but I wish many more of them would, as a frontend platform engineer.by dclowd9901
5/19/2025 at 7:29:34 PM
Am I to understand the earliest version of Zod 4 is 3.25.0?Does this not sound insane?
---
I've been using the alpha versions of Zod for months, I just want to edit package.json and upgrade. But now I need to shotgun across git history instead.
Colin, I appreciate your project immensely. As a point of feedback, you made this ^ much harder than you had to. (Perhaps publish a 4.x along with the 3.x stuff?)
by paulddraper
5/20/2025 at 6:40:14 PM
I understand this as a knee-jerk reaction. I didn't do this lightly.> Perhaps publish a 4.x along with the 3.x stuff
You have some misconceptions about how npm works. Unfortunately it's less reasonable than you think. There's a single `latest` tag, and there's only one "latest" version at a time. It's expected that successive versions here will follow semver. Once I publish zod@4 I can no longer publish additional zod@3.x.x versions. The workaround here is to publish v3 versions to a separate dist tag (zod@three) but anyone consuming that dist-tag (e.g. "zod": "three" in their package.json) loses the ability to specify a semver range.
I recommend reading the writeup[0]. I don't think you're appreciating the magnitude of the disruption a simple major version bump would have caused, or the reasons why this approach is necessary to unlock continuity for Zod's ecosystem libraries. They're quite subtle.
by colinmcd
5/21/2025 at 12:05:13 AM
You can absolutely publish 3.x.x after 4.x.x, they just won’t be “latest” (of course).Speaking bluntly, this isn’t how libraries in the npm upgrade, and pitching this does not inspire confidence.
by paulddraper
5/19/2025 at 6:40:35 PM
The path of least short term pain is often the best path.Everyone old in Python ecosystem remembers the Python 2/3 migration madness.
by miohtama
5/20/2025 at 8:56:27 AM
A heartfelt thank you for not breaking the world like many other libraries do!Breaking changed in fundamental but auxiliary libraries are so painful in the world of frontend development that it's not even funny anymore.
by egorfine
5/20/2025 at 11:17:49 AM
Is there any interop between v3 and v4 schemas themselves? We have an enormous graph of hundreds (if not thousands) of Zod schemas, many inheriting or extending others. It's difficult to envisage us being able to do an incremental upgrade unless both versions can interact neatly.But I am looking forward to seeing if this fixes our issue where in the worst case a 500 line schema file turns into 800,000 lines of generated types.
by andrewingram
5/20/2025 at 6:32:40 PM
No, that kind of interop, especially static interop (assignability), would've been totally unworkable. Despite the length of the changelog, there are very few breaking changes to the user-facing API surface. It's mostly internal/structural changes and deprecations (most of which can be fixed with a find&replace).Report back about that .d.ts issue. It should be far better. That kind of type explosion usually happens when TypeScript needs to infer function/method return types. Zod 4 uses isolatedDeclarations so this kind of thing shouldn't happen.
by colinmcd
5/20/2025 at 1:33:08 PM
No. You will have errors if you try this.by paulddraper
5/20/2025 at 3:49:09 AM
While publishing both versions together helps with compatibility issues, the library now becomes even heavier in environments where tree-shaking is not enabled. In React Native, for example, you have to jump through a lot of hoops to get tree-shaking to work (including getting off the default metro bundler, which can often be a non-starter).by bilalq
5/20/2025 at 3:44:22 AM
I saw your tweet about cracking type inference for recursive schemas. It seems like the solution to this problem is pretty straightforward and simple. Was the solution really as simple as using a getter, or is there some additional complexity that I am failing to grok?by ssousa666
5/23/2025 at 5:35:53 AM
the solution is to avoid TypeScript eagerly validating the object's value type. eager validation breaks cyclic references.by mary-ext
5/23/2025 at 5:37:25 AM
Zod's source code is a bit unwieldy to explain what's going on I feel, so here's a small validation library I wrote that uses the same principlehttps://github.com/mary-ext/atcute/blob/596e023bcb490b16d09a...
by mary-ext
5/19/2025 at 7:13:49 PM
Big thank you for the work on Zod - it's a fantastic library and it's been incredibly valuable for data validation/parsing in Node environments for me over the last two years.by jjice
5/19/2025 at 9:08:36 PM
I had so much difficulty with recursive types mixed with discriminated union (think xml in json for example) hopefully it'll be better nowby waynenilsen
5/19/2025 at 7:09:16 PM
this blog post was a delight to read. great work, colin && thanks!!by transitivebs
5/19/2025 at 9:25:38 PM
A perfectly valid evolution strategy is to add new APIs while keeping the old (which you may or may not choose to deprecate). That is, add the new APIs to 3.x (but probably call them something other than v4.Now, that's effectively what 3.25 is. But there are some problematic extras... the semantics of the semantic versioning is muddled. That means confusion, which means some people will spin their wheels after not initially grokking the situation correctly. Also, there seems to be an implication there could be a strong deprecation of v3 APIs coming. That is, you have to wonder how long the window for incremental migration will remain open.
To be frank, I wouldn't touch zod on any project I hoped will be around for a while. Predicting the future is an uncertain business, but we can look at the past. In a few years, I think it's reasonable to guess zod v4 will be getting the same treatment zod v3 is getting now.
Not that I think you ought to do anything different. I probably wouldn't want to maintain some old API I came up with years ago indefinitely either (not without a decent support contract, that is).
by jmull
5/20/2025 at 11:04:05 AM
Uhm... Wouldn't it make more sense just to publish a `zod4` package?by IshKebab
5/20/2025 at 4:27:33 AM
TLDR: What do I put on my package json to get latest latest latest? "^4" ?by nisten