DEV Community

Cover image for Zod Just Got a Major Upgrade: Here's Everything You're Missing
Shayan
Shayan

Posted on

Zod Just Got a Major Upgrade: Here's Everything You're Missing

Zod is a TypeScript-first schema validation library. You use it to validate and transform data, but also to infer types automatically. It's great if you want typesafe APIs, form validation, or runtime safety without writing everything twice.

At UserJot, I use Zod everywhere: backend, frontend, and in between with tRPC. It's the core of our validation logic and one of the reasons why we can ship features so quickly across our app.

UserJot Dashboard

And now Zod just got a huge upgrade with version 4. If you haven't checked it out yet, here's everything you should know.

Zod v4 brings faster parsing, smaller bundle sizes, better error messages, real JSON Schema support, and a lot of long-requested features that make daily use easier.

Zod v4 Is Way Faster

Let's start with speed:

  • String parsing is ~14x faster
  • Array parsing is ~7x faster
  • Object parsing is ~6.5x faster

TypeScript compile times have also improved, especially for projects using .extend() and .omit() in long chains.

const A = z.object({
  a: z.string(),
  b: z.string(),
  c: z.string(),
  d: z.string(),
  e: z.string(),
});

const B = A.extend({
  f: z.string(),
  g: z.string(),
  h: z.string(),
});
Enter fullscreen mode Exit fullscreen mode

In Zod 3, this could cause a massive amount of type instantiations. Zod 4 cuts that number down dramatically, which helps when working with complex schemas.

Smaller Bundle, Simpler API: Zod Mini

Zod 4 introduces a smaller variant called Zod Mini, which uses a more functional style. It's meant for projects where bundle size matters — like client-side apps or libraries.

Here's the difference:

Classic:

import { z } from "zod/v4";

z.string().optional();
z.object({ name: z.string() }).extend({ age: z.number() });
Enter fullscreen mode Exit fullscreen mode

Mini:

import { z } from "zod/v4-mini";

z.optional(z.string());
z.extend(z.object({ name: z.string() }), { age: z.number() });
Enter fullscreen mode Exit fullscreen mode

Parsing methods like .parse() and .safeParse() still work the same.

Cleaner Error Messages

Zod now has a built-in way to format errors:

try {
  schema.parse(data);
} catch (err) {
  if (err instanceof z.ZodError) {
    console.log(z.prettifyError(err));
  }
}
Enter fullscreen mode Exit fullscreen mode

The output is much easier to read:

✖ Unrecognized key: "extraField"
✖ Invalid input: expected string, received number
  → at username
Enter fullscreen mode Exit fullscreen mode

You can also use a single error option to define custom messages:

z.string().min(5, { error: "Too short." });

z.string({
  error: (issue) =>
    issue.input === undefined ? "Required" : "Not a string",
});
Enter fullscreen mode Exit fullscreen mode

Recursive Schemas Without Hacks

You can now define recursive and mutually recursive schemas directly — no casting needed.

const Category = z.object({
  name: z.string(),
  get subcategories() {
    return z.array(Category);
  },
});

type Category = z.infer<typeof Category>;
Enter fullscreen mode Exit fullscreen mode

It works the way you'd expect and infers the correct types.

JSON Schema Support

You can now convert Zod schemas into JSON Schema directly using:

z.toJSONSchema(schema);
Enter fullscreen mode Exit fullscreen mode

This will include any .describe() or .meta() fields automatically:

const mySchema = z.object({
  name: z.string().describe("Your name"),
  points: z.number().meta({ examples: [10, 20] }),
});
Enter fullscreen mode Exit fullscreen mode

The result:

{
  "type": "object",
  "properties": {
    "name": { "type": "string", "description": "Your name" },
    "points": { "type": "number", "examples": [10, 20] }
  },
  "required": ["name", "points"]
}
Enter fullscreen mode Exit fullscreen mode

Smarter Metadata

You can attach extra metadata to your schemas and manage it through registries.

const myRegistry = z.registry<{ title: string; description: string }>();

myRegistry.add(
  z.string().email(),
  { title: "Email", description: "User's email address" }
);
Enter fullscreen mode Exit fullscreen mode

Zod also includes a global registry:

z.string().meta({
  title: "Email",
  description: "Provide a valid email",
});
Enter fullscreen mode Exit fullscreen mode

New Top-Level Formats

Instead of chaining .email() to a string, Zod now gives you direct format methods:

z.email();
z.uuidv4();
z.url();
z.iso.date();
Enter fullscreen mode Exit fullscreen mode

They're easier to read and less error-prone.

Real-World Features You'll Actually Use

z.file()

For validating uploaded files:

z.file()
  .min(10_000)
  .max(1_000_000)
  .type("image/png");
Enter fullscreen mode Exit fullscreen mode

z.stringbool()

Parse booleans from strings like "true", "false", "1", "0":

z.stringbool().parse("1"); // => true
Enter fullscreen mode Exit fullscreen mode

You can also customize:

z.stringbool({
  truthy: ["yes", "enabled"],
  falsy: ["no", "disabled"],
});
Enter fullscreen mode Exit fullscreen mode

Refinements Now Chain Properly

You can now refine and still call other methods like .min() afterward:

z.string()
  .refine((val) => val.includes("@"), "Must be email-like")
  .min(5);
Enter fullscreen mode Exit fullscreen mode

z.literal([...])

You no longer need to write long unions of literals:

z.literal([200, 201, 202, 204]);
Enter fullscreen mode Exit fullscreen mode

z.templateLiteral()

Define template literal types like:

z.templateLiteral(["hello, ", z.string()]);
// → `hello, ${string}`

z.templateLiteral([z.number(), z.enum(["px", "em", "rem", "%"])]);
// → `${number}px` | `${number}em` | ...
Enter fullscreen mode Exit fullscreen mode

Small but Nice Improvements

  • z.int32(), z.uint64() for fixed-width numbers
  • .overwrite() for transforms that don't change the type
  • z.discriminatedUnion() now supports more complex structures (like nested discriminators)

Final Thoughts

At UserJot, we help SaaS teams collect feedback, share their roadmap, and announce product updates — all from one place. It's built for product teams who want to stay close to their users and keep everyone in the loop.

UserJot Public Dashboard

We use Zod throughout the entire codebase, on the backend for validating input, and on the frontend with tRPC to keep everything typesafe across the stack. With Zod v4, things are faster, cleaner, and easier to maintain.

If you're already using Zod, the upgrade is worth trying. And if you're new to it, this version is a great place to start.

Top comments (3)

Collapse
 
nathan_tarbert profile image
Nathan Tarbert

pretty cool seeing a tool like zod keep pushing things forward - i always start thinking about how much solid validation actually matters in day-to-day building. you ever feel like most projects skip this stuff and pay for it later?

Collapse
 
dotallio profile image
Dotallio

Love how much faster and clearer Zod v4 is, especially the new error formatting - it saves me so much debugging time.

What was the hardest part to update in your app when switching to v4?

Collapse
 
saqibtanveer-dev profile image
Saqib Tanveer

I like to use tools and packages which get updates simultaneously.