Zod 4

434 bpierre 157 5/19/2025, 3:24:58 PM zod.dev ↗

Comments (157)

colinmcd · 1h ago
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.

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

elbajo · 1h ago
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)

colinmcd · 1h ago
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 :)

elbajo · 32m ago
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.

miohtama · 1h ago
The path of least short term pain is often the best path.

Everyone old in Python ecosystem remembers the Python 2/3 migration madness.

jjice · 1h ago
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.
transitivebs · 1h ago
this blog post was a delight to read. great work, colin && thanks!!
paulddraper · 58m ago
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?)

rafram · 4h ago
> To simplify the migration process both for users and Zod's ecosystem of associated libraries, Zod 4 is being published alongside Zod 3 as part of the zod@3.25 release. [...] import Zod 4 from the "/v4" subpath

npm is an absolute disaster of a dependency management system. Peer dependencies are so broken that they had to make v4 pretend it's v3.

gejose · 4h ago
> Peer dependencies are so broken that they had to make v4 pretend it's v3

I'm not sure this is the right conclusion here. I think zod v4 is being included within v3 so consumers can migrate over incrementally. I.e refactor all usages, one by one to `import ... from 'zod/v4'`, and once that's done, upgrade to v4 entirely.

skydhash · 2h ago
How do you even migrate incrementally? why keep both old and new code together like a Frankeinstein project? Especially on a codebase you own. It's a library, not a platform.
colinmcd · 1h ago
This is not done for Zod's benefit. It's done for the benefit of libraries that depend on Zod, and the users of those libraries. If a library wants to add "incremental" support for Zod4 (that is, without dropping support for Zod 3 and publishing a new major), they need access to types (and possibly runtime code) from both libraries to do so in a sound way. I detail a number of other approaches for achieving this here[0] and why they ultimately fall short. Ultimately npm wasn't designed for this particular set of circumstances.

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

diggan · 2h ago
> why keep both old and new code together like a Frankeinstein project?

It seems like Zod is a library that provides schema to your (domain) objects in JS/TS projects, so if you're all-in with this library, it's probably a base-layer for a ton of stuff in the entire codebase.

Now imagine that the codebase is worked on by multiple parties, in different areas, and you're dealing with 100K-1M lines of code. Realistically, you can't coordinate such a huge change with just one "Migrated to Zod 4" commit and call it a day, and you most likely want to do it in pieces.

I'm not convinced publishing V4 as version 3 on npm is the best way of achieving this, but to make that process easier seems to be the goal of that as far as I understand.

skydhash · 2h ago
If it's part of your core domain, I'd say that one of the reasons you want the feature set and the API to be as stable as possible. And your core domain should probably be isolated from the things that depends on it in such way that only business logic ripples out.

One of the strange things from the NPM/JS culture is the focus to write everything in one language leading to everyone forgetting about modularization and principles like SOLID. We link everything together with strange mechanisms for "DX", which really means "make it easy to write the first version, maintenance be damned".

mmcnl · 1h ago
This comment feels very detached from reality. Migrating core dependencies in large projects is always cumbersome. One universal truth is that you can make complex tasks easy by breaking it up into small pieces. Providing a sensible forward migration path like what Zod does causes 0 harm and makes everyone's life easier. This has absolutely nothing to do with "NPM/JS" culture, whatever that means.
Sammi · 1h ago
You use Zod to define all the schemas and types used in "your core domain". It is the base layer tool of your business logic and models, and for defining your interfaces. And yes it makes it possible to share all of this between the frontend and backend, which is a tremendous improvement in both correctness and in development productivity.

You don't get anything remotely like this in any other backend frontend combo.

skydhash · 44m ago
I get the wish to have that, but IMO, it's a flawed approach. The frontend and the backend are generally different domains. Initially, their model may look alike, but they will probably differs during the lifetime of the project as they serve different needs. That's why the shape of the data stored in the DB differs from the one involved in business logic, which differs from the DTOs, which can differs from the objects involved in the presentation.

In simple applications, you can get away with defining a single schema for all, but it helps with keeping in mind that you may have to design a better data shape for some logic.

rafram · 2h ago
Yeah, importing two versions could allow you to pass v3 objects to v4 methods and vice versa, and that seems like an extremely bad idea (if it would even type-check / run).
colinmcd · 2h ago
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

Lvl999Noob · 33m ago
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.
codeflo · 1h ago
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.
colinmcd · 1h ago
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

Trufa · 2h ago
I feel like people will upvote anything negative. This is not much a limitation of npm, there's nothing intrinsically wrong with npm that lead to this decision, this is more a pragmatic way of allowing progressive change of a library that introduced a lot of braking changes.
danenania · 2h ago
Right, you have the same issues to consider when shipping a breaking major version upgrade to a new library in any language/ecosystem.

That said, you do see a cultural difference in node-land vs. many other ecosystems where library maintainers are much quicker to go for the new major version vs. iterating on the existing version and maintaining backward compatibility. I think that's what people are mostly really referring to when they complain about node/npm.

Webpack is a good example—what's it on now, version 5? If it was a Go project, it would probably still be on v1 or maybe v2. While the API arguably does get 'better' with each major version, in practice a lot of the changes are cosmetic and unnecessary, and that's what frustrates people I think. Very few people really care how nice the API of their bundler is. They just want it to work, and to receive security updates etc. Any time you spend on upgrading and converting APIs is basically a waste—it's not adding value for users of your software.

skydhash · 34m ago
This very much. If I'm using your library, I've already committed to it's architecture and API with all its flaws. And my users don't care for the technology I use. Even if they're not that good, I can build wrapper over the worst aspects and then just focus on building features. New features are nice, but I'm more interested in getting security updates for the current version that I have.

When it's time to go for a refactoring, the trade-off between costs and returns are worth it as you can go for years between those huge refactors.

herrkanin · 3h ago
I might be blindsided by using npm exclusively for years by this point, but what would be a better way to support iteratively migrating from v3 to v4 without having to do it all in one large batch?
diggan · 2h ago
One possible solution: Publish a new package with a new name. I've personally been thinking of doing that for some libraries, where I'd commit to never change the public API, and if I figure out a nicer way of doing something, I'd create a new library for it instead, and people who wanna use/maintain the old API can continue to do so.
Sammi · 46m ago
People wouldn't change to the new one, because names are extremely sticky, so what's the point?
diggan · 21m ago
Ok, that's fine by me. They can continue using the original library for whatever reason they want, including because of the name.
skydhash · 32m ago
If they need the new one, they will switch to it. And you have the right to drop support for the old one after a while (hopefully giving everyone the time to migrate).
strken · 2h ago
Using npm's built in support for package aliases to simultaneously install zod@3 as zod and zod@4 as zod-next?

Edit: reading the rationale, it's about peer dependencies rather than direct dependencies. I am still a little confused.

swyx · 2h ago
apart from the other things people raised, as leerob also points out it hints to an llm that the api may be different https://x.com/leerob/status/1924504939818008791
derN3rd · 4h ago
We've been using zod 4 beta already with great improvements but due to our huge codebase not being able to handle the required moduleResolution settings, we cannot upgrade...

They could at least also publish it as a major version without the legacy layer

EDIT: I've just seen the reason described here: https://github.com/colinhacks/zod/issues/4371 TLDR: He doesn't want to trigger a "version bump avalanche" across the ecosystem. (Which I believe, wouldn't happen as they could still backport fixes and support the v3 for a time, as they do it right now)

sroussey · 3h ago
Would not be hard for you or anyone else to do that as well.
827a · 2h ago
Yeah this seems like very poor reasoning tbh.
fhd2 · 4h ago
I feel like both Node.js and NPM were initially vibe coded before LLMs existed. Just a quick hack, kind of, that got hugely popular somewhat by accident.

Edit: Thinking about it, that's the origin story of JavaScript as well, so rather fitting.

johnfn · 4h ago
I'm curious if anyone here can answer a question I've wondered about for a long time. I've heard Zod might be in the right ballpark, but from reading the documentation, I'm not sure how I would go about it.

Say I have a type returned by the server that might have more sophisticated types than the server API can represent. For instance, api/:postid/author returns a User, but it could either be a normal User or an anonymous User, in which case fields like `username`, `location`, etc come back null. So in this case I might want to use a discriminated union to represent my User object. And other objects coming back from other endpoints might also need some type alterations done to them as well. For instance, a User might sometimes have Post[] on them, and if the Post is from a moderator, it might have special attributes, etc - another discriminated union.

In the past, I've written functions like normalizeUser() and normalizePost() to solve this, but this quickly becomes really messy. Since different endpoints return different subsets of the User/Post model, I would end up writing like 5 different versions of normalizePost for each endpoint, which seems like a mess.

How do people solve this problem?

stephen_hn · 4h ago
I think you would use discrimated unions.

const MyResult = z.discriminatedUnion("status", [ z.object({ status: z.literal("success"), data: z.string() }), z.object({ status: z.literal("failed"), error: z.string() }), ]);

You can define passthrough behavior if there are a bunch of special attributes for a moderator but you don't want to list/check them all.

With different methods that have different schema- If they share part of the same schema with alterations, you can define an object for the shared part and create objects that contain the shared object schema with additional fields.

If you have a lot of different possibilities, it will be messy, but it sounds like it already is for you, so validators could still at least validate the messines.

johnfn · 4h ago
Hmm, this is pretty interesting, but I still have issues. While Zod seems to be able to transform my server-returned object into a discriminated union, it doesn't seem to be able to tell which of the unions it is. For instance, say that api/user/mod-panel (e.g.) returns only moderator user objects. My API endpoint already knows that it will have moderator-related fields, but Zod forgets that when I parse it.
Scarblac · 3h ago
Then you don't use the discriminated union for that endpoint, but the schema that only accepts moderator user objects.

That's not surprising, "this endpoint will only return moderator user objects" is a bit of knowledge that has to be represented in code somehow.

Sammi · 38m ago
You can define all the different sub types separately and make the discriminated union using them. The use only a single type for an endpoint if it only uses a single type. Only use the discriminated union where you actually might be handling multiple types.
stephen_hn · 4h ago
I am not totally sure what your API's look like but if you know the API only has a certain type (or subset of types), you would only validate that type on the method. Don't use a single union for everything.

Here is some pseudocode.

Person = { name: string, height: number }

Animal = {name: string, capability: string}

A = { post: object, methodType: string, person: Person }

ModeratorA = { post: object, moderatorField1: string, moderatorField2: string, person: Person }

UnionA = A && ModeratorA (There's probably a better way of defining A and ModeratorA to share the shared fields)

B = { post: object, animal: Animal }

endpoint person parses UnionA

endpoint animal parses B

You don't put all of your types in one big Union.

vjerancrnjak · 3h ago
You can use an ‘is’ method to pick out a type from union. Although, if union is discriminated, it’s discriminated with a ‘kind’ so you should be able to know which kind you received.
probabletrain · 4h ago
In an ideal world you'd have one source of truth for what the shape of a User could be (which may well be a discriminated union of User and AnonymousUser or similar).

Without fullstack TS this could look something like: (for a Python backend) Pydantic models+union for the various shapes of `User`, and then OpenAPI/GraphQL schema generation+codegen for the TS client.

johnfn · 4h ago
The problem with this is that your One True User shape tends to have a bunch of optional properties on it. e.g., in the user profile you fetch Post[], but in the user directory you don't, and so on with other joined properties. If every endpoint returns the One True User, then you end up needing to write conditional logic to guard against (say) `.posts` when you fetch users in the profile, even though you know that `.posts` exists.
probabletrain · 3h ago
Not sure how other stacks solve this, but with GraphQL the backend defines a `User` type with a full set of fields, and the client specifies only the fields it wants to query. And with codegen you get type safety.

So on the /posts page the client asks for `{ user: { id, posts: { id, content }[] } }`, and gets a generated properly-typed function for making the query.

iainmerrick · 4h ago
In Typescript at least, if the discriminated union is set up correctly, you just need a single check of the type field. That lets TS narrow the type so it knows whether e.g. `.posts` is present or not.
johnfn · 4h ago
I think I can do this, yes, but it becomes a very large amount of repetitive work.

Let's say I have api/profile (which has `.posts`) and api/user-directory (which does not). I define User as a discriminated union - one has type `user-with-posts` and one has type `user-no-posts`. OK, good so far. But now say I have a photo gallery, which returns photos on user. Now I have to add that into my discriminated union type, e.g. `user-with-posts-and-photos`, `user-with-posts-but-not-photos`, `user-with-no-posts-but-yes-photos`... and it gets worse if I also have another page with DMs...

joshfee · 3h ago
You can use a string union to discriminate when it makes sense, but that's not the only way to discriminate and in this case you'd instead use the presence of the items themselves (essentially duck-typing with strong type guarantees)

Typescript playground: https://www.typescriptlang.org/play/?#code/C4TwDgpgBACg9gZ2F...

johnfn · 1h ago
Now you run into the issue I mentioned in GP, where you end up writing `if (blah)` everywhere, even though you know that `blah` is definitely present.
Scarblac · 3h ago
Change the design of the API so that the posts and the photos and so on are not returned inside the user object but on the same level as it.

So {user: {...}, photos: [...]}, not {user: {..., photos: [...]}}.

Alternatively define separate schemas for each endpoint that extend the base User schema. But I'd prefer having the same structure everywhere as much as possible.

mmcnl · 1h ago
Your server should export the types. Don't write types by hand in your client, that makes no sense. The server knows what data it is sending and should provide that information as metadata to the client. Practically this can mean that a Python backend uses Pydantic schemas which can be used to automatically generate an OpenAPI specification that your client can use to generate types.
johnfn · 1h ago
This answer is how I wish the world would work, but I am stuck with a server with a poor type system that can't refine the types nearly as accurately as TS can. :(
adpirz · 4h ago
It's hard to unpack without knowing more about the use case, but adding discriminant properties (e.g. "user_type") to all the types in the union can make it easier to handle the general and specific case.

E.g.

if (user.user_type === 'authenticated') {

  // do something with user.name because the type system knows we have that now

}
gavinray · 4h ago
This doesn't help in your case, but this is what GraphQL was invented for.

TS libraries for GQL queries can dynamically infer the response shape from the shape of the selected fields.

Such that the query

    query {
        user {
            username
            posts {
                text
            }
        }
    }
Would be

    type Response = {
        user: {
            username: string
            posts: Array<{
                text: string
            }>
        }
    }
And a query with just { user { username } } would have the posts property omitted entirely.
peab · 4h ago
Either Unions, or optional fields
satvikpendem · 4h ago
Zod 4 looks good but even with their latest improvements, ArkType is still an order of magnitude faster. Sometimes for the sake of backward and syntax compatibility, it is difficult to make something much faster than a fully greenfield newer library. We recently did an analysis of all these types of tools for our project and decided to go with ArkType for partially this reason, the other was TypeScript ergonomics felt nicer.
dangoodmanUT · 1h ago
ArkType is a nightmare to use, zod is nice to use
lhnz · 40m ago
Out of interest, why is it a nightmare to use?

I've always been worried about how overly clever the approach is, does it have problems?

atonse · 4h ago
I was looking at all their speed metrics, and can you explain to me where the speed makes a difference?

We only use zod to validate forms, so I keep thinking "how does this matter?" Are people maybe using it to validate high throughput API input messages or something like that, where performance may matter more?

satvikpendem · 4h ago
Yes, we use it to validate the API responses from the backend (as well as type validation on the backend itself from any frontend POST requests), and especially on the client side, speed and bundle size is very important.
yhprum · 3h ago
Does arktype not come with a larger bundle size than zod? That was the reason I was shying away from it at the moment, especially with the bundle size reductions with zod 4 as well

[1] https://www.reddit.com/r/typescript/comments/1i3ogwi/announc...

vlovich123 · 1h ago
Yeah, I can't see zod really adding that much overhead in the critical latency path for all but extremely large responses. Sometimes people pick "the fastest" library without considering whether the performance is relevant for a given code path.
andrewflnr · 4h ago
Interesting, that one didn't even appear in my research. I was specifically looking for typescript ergonomics too. Probably still not going to switch away from Zod, though.
sroussey · 3h ago
Why ArkType over TypeBox?
8s2ngy · 4h ago
Congratulations to the Zod team on the new release. At the risk of sounding overtly negative, I can't help but shudder when I think about the number of breaking changes outlined in the migration guide. For projects that rely heavily on Zod, it feels like a daunting task ahead—one that will demand a lot of developer attention and time to navigate. Having maintained a few frontend projects that are 4-5 years old at work, I really empathize with them.

In my experience, large React projects often depend on a multitude of libraries, and when each one rolls out substantial changes—sometimes with barely any documentation—it can quickly become overwhelming. This is honestly one of my least favorite aspects of working with JavaScript. It just feels like a constant uphill battle to keep everything in sync and functioning smoothly.

nicksergeant · 3h ago
I am 100% in agreement here. I operate a couple large Next.js apps and in the last year we've had to deal with Next.js 14 -> 15 which introduced a ton of breaking cache changes, Next.js pages -> app router, React 18 -> 19, Eslint 8 -> 9, and Tailwind 3 -> 4.

It's honestly been a nightmare, and I wish I had just built in Django instead. The Tailwind 3 -> 4 migration was probably among the most painful, which I was not expecting.

winstonp · 3h ago
i am just simply not touching tailwind v3 -> 4. v4 is for new projects only.
Daishiman · 3h ago
I will continue to beat the drum that using easy-to-migrate frameworks like Django that don't go around introducing major breaking changes is one of the bigger web development superpowers.
hombre_fatal · 3h ago
Django doesn't run in the browser though. All of the tech they listed can run in the browser which gives you other superpowers at the expense of having moving targets, but that's what they opted into when they decided to run code on user machines instead of in a single server environment.
skydhash · 2h ago
The main browser API is pretty stable, all things considered. It's just that people can't seem to stay put on a given design, instead trying everything under the sun.

When you have something like SDL which is at it's third version at 27 years old, I'm very doubtful about the culture of NPM/JS world. A closer example is jQuery which is also in its third version at 18 years old.

hombre_fatal · 33m ago
True, the browser API might be stable, but the best way to build stateful client applications is unstable.

It's a constantly improving and experimental domain - not just on the web but also the desktop and mobile environment.

Next.js is a good example since it (and its competitors) are a natural iteration on top of SPAs where you want the first request to the server to also inline the initial state of the SPA to remove the double-request problem.

But you pay a huge price trying to live on the edge like that, and when you run into issues, it doesn't make much sense to call that the state of web development since you could have used boring server tech or even a boring React SPA which also hasn't changed much in a decade.

ruined · 4h ago
that's why they're taking the dual-availability approach, with a separate 'mini' edition. it's easy to perform a progressive migration without messing with the package manager.

consumers uninterested in the 'mini' edition don't have to bother with that part.

but, the benefits of the 'mini' edition are so drastic for tree-shaking that it was driving development of alternatives - zod had to either do it (and benefit), or deprecate.

JoRyGu · 4h ago
Was just looking at their release strategy. This is being handled by people that have experienced the hell that is dependency management in the JS ecosystem. Kudos to them.
koakuma-chan · 4h ago
> For projects that rely heavily on Zod, it feels like a daunting task ahead—one that will demand a lot of developer attention and time to navigate.

Or just use an LLM.

cscheid · 4h ago
This is the kind of task that LLMs are precisely terrible at; there isn't an abundance of Zod 4 examples, and the LLM will sure as shit will give you _something_ you are now by definition ill-equipped to assess.

I'm confident about this assessment because I maintain a large-ish piece of software and perenially have to decipher user reports of hallucinated LLM syntax for new features.

koakuma-chan · 4h ago
Are you sure that's not a skill issue? Zod v4 has .mdx documentation which can be given to the LLM as context, including a migration guide. A reasoning LLM should be able to manage.
collingreen · 3h ago
"It will solve all your problems!"

"It didn't solve my problems"

"You're the problem!"

koakuma-chan · 3h ago
> "You're the problem!"

He said users sent reports with hallucinated syntax, he wasn't even the one who used LLMs.

camgunz · 4h ago
And then pick through all the LLM's mistakes.
rvnx · 4h ago
Ask another LLM to pick for you
sensanaty · 3h ago
LLMs can't even not write React when you explicitly tell them not to write React in your Vue codebase. I still, with Gemini 2.5 Pro and Claude 3.7 both, get this annoying ass interaction almost daily, despite a universe of context there that should make it obvious I don't want a React component in my Vue codebase.
owebmaster · 4h ago
better yet: use an LLM to generate a subset of Zod that is fit for the project
causal · 4h ago
Zod is a lot better than some of the alternative solutions I've seen. That said, the need for this sort of explicit validation always felt like a failure of where modern web development has taken us. It's so frustrating how full-stack development requires so many ways of describing the same shapes: JS input validation, Swagger for API definition, server-side input validation, ORM for schema conformance, and TypeScript often requiring separate definitions on server and client side. It's so tedious.
jauntywundrkind · 4h ago
TypeScript's insistence on being a static checking/compile time only system is such a sad wound to the whole ecosystem. I don't want TypeScript to be a runtime checker, but I want it to export useful usable type data for classes, functions, objects. TypeScript feels like the best source of truth we have, but instead so many of the attempts to reflect on what we have are all going it alone, having to define their own model and their own builders for describing what stuff is. You've mentioned 5 major areas where reflection is a core part of the mission, and each of these have multiple implementations.

There is the old TypeScript type emitter, reflect-metadata, which would provide some type information at runtime, but it targets very old decorator and metadata specifications, not the current version. I don't super know how accurate or complete it's model really is, how closely it writes down what typescript knows versus how much it defines its own model to export to. https://www.npmjs.com/package/reflect-metadata

We are maybe at the cusp of some unifying, some coming together, albeit not via typescript at this time, still as a separate layer. The Standard Schema project has support from I dare say most of the top validation libraries. But extending this to API definitions, ORM tools is in extremely early stages. https://github.com/standard-schema/standard-schema?tab=readm...

causal · 1h ago
Yes. Although I am more familiar with TypeScript and prefer it for a few reasons, I've been inspired seeing how Pydantic has been used in the field for both design-time and runtime validation.
koolba · 4h ago
But that’s the whole point of something like this. You do it once and dynamically generate everything else downstream. So change it once in the zod schema and it propagates with type checking through your entire app.

The zod schema becomes the source of truth.

esafak · 4h ago
Does it? I don't use Zod on my JVM backend. How would I?

In addition to using OpenAPI, I generate TS interfaces from my data classes in a Gradle task.

worldsayshi · 4h ago
Does zod really support that? What if the code base start out with a go backend and a TS/JS component is only added later? It would be nice if the source of truth was a bit more language agnostic.
oxidant · 4h ago
Better to use something like OpenApi and generate your zod schema using it.
kellengreen · 4h ago
Does it?

Most Go, Java, and Python APIs are practically all Swagger based.

tshaddox · 2h ago
> It's so frustrating how full-stack development requires so many ways of describing the same shapes: JS input validation, Swagger for API definition, server-side input validation, ORM for schema conformance, and TypeScript often requiring separate definitions on server and client side. It's so tedious.

Many of the same people who complain about how complicated modern web dev is would also shudder at the suggestion to just replace all those things with TypeScript (and Zod, if that's your TS schema definition and validation library of choice).

90s_dev · 4h ago
All APIs are experimental and in a state of constant flux and evolution, including the web.

The only time status quo is fine is when it's for clients and employers who just want stuff done and don't care how.

In that context, all these unfortunate layers of complexity at least mean more billable hours.

owebmaster · 4h ago
> All APIs are experimental and in a state of constant flux and evolution, including the web.

for better or worse the web only adds, don't change or remove APIs

satvikpendem · 4h ago
How else would you do it? You could use something like trpc for full end to end type safety but that requires using TypeScript on both the frontend and backend (which not everyone does) and also locking yourself into only the web platform (so no mobile, etc).
blackoil · 4h ago
We use drizzle + Hono + client with zod. So the entire chain from ui to client to server to DB follow same validation and shape.
causal · 33m ago
I've heard of this stack, glad to hear it works, still hate that you need three separate external dependencies to do it
worldsayshi · 4h ago
Yes there should at least be a way to consolidate so that we can agree on format to treat as the source of truth that we can generate the other formats from.
crab_galaxy · 4h ago
OpenAPI TypeScript is the closest thing I’ve found to perfection when your API is written in a different language than your client.

https://openapi-ts.dev/

probabletrain · 4h ago
GraphQL is another one, with schema introspection and codegen for queries and types.
varbhat · 4h ago
I am not an expert here but I had a thought that JSON-Schema might be a good choice because since it's schema based, i can implement the validators in Non-Typescript languages too.

https://ajv.js.org is one such JSON Schema library. How does zod compare to this?

rafram · 4h ago
Well, Zod isn't just for validating JSON. It supports validating objects that can't be represented in JSON without transformation (dates, class instances, and so on). You can also use it as your JSON transformer if you want to - you can write your schema so it accepts strings, validates them as ISO date strings, and outputs Date objects, for instance.
nozzlegear · 3h ago
> Well, Zod isn't just for validating JSON. It supports validating objects that can't be represented in JSON without transformation (dates, class instances, and so on).

Thanks, this was the missing piece for me. I'd been thinking about using Zod for an old client's Node codebase, but I'd only need it to validate the shape of json objects received by http endpoints. Zod was looking like overkill, but I know it's popular so I wasn't sure what I was missing.

0x62 · 4h ago
Zod 4 supports converting a Zod schema to JSON-Schema (natively, this has always been possible with 3rd-party libs).

One key difference is preprocessing/refine. With Zod, you can provide a callback before running validation, which is super useful and can't be represented in JSON. This comes in handy more often than you'd think - e.g converting MM/DD/YYYY to DD/MM/YYYY before validating as date.

silverwind · 1h ago
Does it support the other way around too? I'd live to ditch AJV.
silverwind · 1h ago
JSON Schema is the way for cross-language validation. Wrap it in OpenAPI and you also get nice API docs for free.
sroussey · 3h ago
It is a good choice and in that case TypeBox is a decent way to generate it.

And you can use AVJ or its own schema validation system (which is much faster, but only sync where avj has asynchronous validation options in case you want to check against a database or something).

punkpeye · 2h ago
Shocked by the negativity around this.

I tested early versions of zod v4 and liked the new API, but was very concerned about what will be the migration path. I was even going to suggest to publish under a new package name.

But the author's approach is ingenious. It allows for someone like me to start adopting v4 immediately without waiting for every dependency to update.

Well done!

labadal · 3h ago
I just got started working Zod into a new project. This could not have happened at a better time. I would have needed to change so much ot migrate to v4 based on what I'm seeing.
__natty__ · 42m ago
Great job! I love support of json conversion you built-in. However, I find top level string bad choice as it was more verbose before, to have them under string() namespace.
bobbyraduloff · 34m ago
great job on zod. it’s an incredible library. also really excited about the locale errors. finally I can push the zod errors directly to the frontend. would love to take responsibility for the BG locale.
jasonthorsness · 4h ago
But how is the below possible - doesn't it need to include most of the TypeScript compiler? Does it compile the type definitions to some kind of validation structure (like how a compiled regex works) and then use just that?

    - Zero external dependencies
    - Works in Node.js and all modern browsers
    - Tiny: 2kb core bundle (gzipped)
_tqr3 · 4h ago
The TypeScript compiler is only needed during development, the compiled JavaScript code contains no TypeScript-specific logic. Zod’s validation is entirely JavaScript-based, relying on simple checks (e.g., typeof, regexes, comparisons).

Also, the 2kb bundle just for the zod/v4-mini package, the full zod/v4 package is quite large.

Aeyxen · 3h ago
I think Zod uses JIT compilation via `new Function`, rather than including the entire TypeScript compiler. This method allows for concise validation logic, executing only what’s necessary at runtime.
mac9 · 4h ago
The little animation that highlights what section you are reading currently is beautiful
sroussey · 3h ago
Hopefully someone can compare Zod4 to TypeBox.

Last I looked, the nice thing about TypeBox was that is _was_ JsonSchema just typed which was nice for interoperability.

neilpa · 4h ago
We're currently evaluating both Zod and ArkType as a replacement for earlier JSON schema validations. The thing I can't get over with Zod is the syntax is _so_ different from defining TS types.

Are there reasons to go with Zod over ArkType?

CharlieDigital · 4h ago
You might like Typia better.

I recently put together a deck for this after some investigation: https://docs.google.com/presentation/d/1fToIKvR7dyvQS1AAtp4Y...

fkyoureadthedoc · 4h ago
On the ArkType website they talk about speed and DX, but no mention of bundle size. I assume, and maybe this is an unfair assumption, that's because it's bad. I haven't come across ArkType until just now, but I was just checking out Valibot today because it's got a small bundle size.
rafram · 4h ago
colinmcd · 1h ago
These numbers don't reflect anything useful. This is the total size of the code in the package, most of which will be tree-shaken. In Zod's case, the package now contains three independent sub-libraries. I recommend plugging a script into bundlejs.com[0] to see bundle size numbers for a particular script

[0] https://bundlejs.com

satvikpendem · 4h ago
We also chose ArkType after evaluating Zod, Valibot, and Effect Schema, among others. It seemed to have the best developer experience while also being much faster. It also supports the Standard Schema project which I believe Effect Schema does not fully support.
mapcars · 2h ago
Could you elaborate on how exactly do you use these? I was assuming its for some kind of contract testing, but there are mentions of bundle size, is it used on fetching data to fail early if the format is wrong, something like that?
bjacobso · 4h ago
I believe Effect Schema fully supports Standard Schema, the issue is that it supports much more than Standard Schema, so not all schemas will work and thus provide compile time errors.
satvikpendem · 4h ago
Yes that's correct, it's more powerful but we wanted to maintain full compatibility just for the future as well. But if you're all in on the Effect ecosystem, it's quite nice.
sesm · 4h ago
Does ArkType support generating data from schema?
petesergeant · 4h ago
Everyone else is using Zod so the LLMs understand it well, there’s a big ecosystem, it’s unlikely to disappear overnight, and new hires may well already use it. It’s the boring solution.
nikolay · 3h ago
Zod and the way to define schemas with it has been one of the reasons I didn't want to touch TypeScript as it's becoming more prevalent!
rlt · 3h ago
Can you elaborate?
aduffy · 3h ago
Seeing v4-mini reminds me of an LLM. I can only assume the next major release will be zod-3.9-medium-high
TheFlashBold · 2h ago
Or maybe 3.8-q4_K_M for a quantized version
90s_dev · 4h ago
> TypeScript-first schema validation with static type inference

Is there a comparison guide? Never heard of this before, but I used io-ts and ajv.

indigovole · 4h ago
With yet another exciting new release of something reaching the top of HN, I would just like to urge devs to put a description of the project they're actually releasing and a link to the page describing the project in the release announcement.

These announcements could be a valuable touchpoint for you to reach a whole new audience, but I can't remember a single one that starts with something like "exciting new release of NAME, the X that does Y for users of Z. Check out the project home page at https:// for more."

Quite often, the release announcement is a dead end that can't even take me to the project! In this case, the only link is a tiny octocat in the lower left-hand corner, AFAICS.

rossant · 2h ago
I came here to say this. I clicked on the link and had no idea what this was about.
baalimago · 2h ago
I read this, and I find myself not wanting to migrate since I know that LLMs can't generate Zod 4 syntax yet.
devin · 4h ago
Seeing an announcement for a new version of some typescript library I've never heard of shoot to the top of the front page makes me feel extremely out of touch with mainstream dev.
esafak · 4h ago
What do you use for validation in TS? Anyone who deals with data should be validating/parsing it.
90s_dev · 4h ago

  const cached = new Map<string, Function>()

  export function as<T>(o: any, path: string, as: (o: any) => T | undefined) {
    try {
      let fn = cached.get(path)
      if (!fn) cached.set(path, fn = new Function('o', `return o.${path}`))
      const v = fn(o)
      return as(v) as T | undefined
    }
    catch (e) {
      return undefined
    }
  }

  as.number = (o: any) => (typeof o === 'number' ? o : undefined)
  as.string = (o: any) => (typeof o === 'string' ? o : undefined)
  as.boolean = (o: any) => (typeof o === 'boolean' ? o : undefined)
  as.numbers = (len = 0) => (o: any) => (o instanceof Array && o.length >= len && o.every(c => typeof c === 'number') ? o : undefined)
  as.strings = (len = 0) => (o: any) => (o instanceof Array && o.length >= len && o.every(c => typeof c === 'string') ? o : undefined)


  const size = as(usrConfig, 'sys.size', as.numbers(2))
  const fontpath = as(usrConfig, 'sys.font', as.string)
rafram · 4h ago
Great if that's the only validation you need, but I can't imagine using it in a real app, and there are some obvious bugs (e.g. never use `instanceof Array`).
90s_dev · 4h ago
Array.isArray isn't needed when you know your array isn't going to be deserialized e.g. via MessagePort. What other supposed bugs?

And yes I'm aware this doesn't have a huge API surface. That's the whole point. If I already have a JSON object, I can reach into it and get either what I ask for or nothing. In many real world cases, this is enough.

rafram · 3h ago

    let someArray = [1, 2, , 4];
    console.log(as.numbers(someArray) === someArray); // => true
    
    for (let number of numbers) {
        // This should be safe because I know everything in the array is a number, right?
        console.log(number.toFixed(2)); // => TypeError: number is undefined
    }
I mean, it's probably fine if you're only ever getting your data from JSON.parse(). But I would hesitate to use this in production.
90s_dev · 50m ago
The point is that it's layered.

1. Validate your Buffer/UInt8Array for valid size/encoding/etc first

2. Parse it to an object via JSON.parse or whatever your use-case needs

3. Reach into it with this function to get data if it matches the type you need

This code only deals with #3 and makes a few assumptions about #2 (e.g. no undefined, typical JSON-like object, etc).

satvikpendem · 4h ago
If you don't do web dev (or at least, the latest web dev) then it's understandable, I often see lots of posts in the top spots whose languages I have no idea about either.
brulard · 1h ago
Zod is quite common, if not the most used validation library for JS/TS projects.
h1fra · 3h ago
Impressive results, we need some deep dive on the performance improvements!
ar-nelson · 4h ago
Obligatory shameless plug whenever Zod is posted: if you want similar, but much more minimal schema validation at runtime, with a JSON representation, try Spartan Schema: https://github.com/ar-nelson/spartan-schema
jilles · 4h ago
When I see a repository with many files "updated 4 years ago" I'm usually inclined to think it's abandoned.
ashwinsundar · 4h ago
Or the code is done and doesn’t need to be iterated on continuously?
jzig · 4h ago
Then say so in the readme :)
_tqr3 · 4h ago
Still no hapi support?
dankobgd · 3h ago
i would use it if there was just 1 normal zod lib not like how it is now: zod and zod mini and who knows what. I rather use arktype or valibot if i am in browser only.
revskill · 4h ago
Mind blowing.
cluckindan · 4h ago
Between GraphQL and Zod, there really is no comparison. The latter feels clunky and superfluous by design when the former ecosystem can smoothly provide static TS typings without extra work or schema duplication.
eknkc · 4h ago
Also, I think teflon pans are much better than squids.
lyu07282 · 4h ago
Squid ink is black so are teflon pans, the latter feels clunky and superfluous by design when the former type of fruit can smoothly provide vitamins to vertically integrate with our stack.
gejose · 4h ago
Ah yes two totally different technologies - let's compare them like they're designed to solve the exact same problem
cluckindan · 2h ago
GraphQL is a different animal, but I was only comparing the features Zod implements to those found in the GraphQL ecosystem.