💎 Zod 4 is now stable!  Read the announcement.
Zod logo

Notas de la versión

Después de un año de desarrollo activo: ¡Zod 4 ya es estable! Es más rápido, más ligero, más eficiente con tsc e implementa algunas características solicitadas desde hace mucho tiempo.

❤️

Muchas gracias a Clerk, quien apoyó mi trabajo en Zod 4 a través de su extremadamente generosa OSS Fellowship. Fueron un socio increíble durante todo el proceso de desarrollo (¡mucho más largo de lo previsto!).

Versionado

Para actualizar:

npm install zod@^4.0.0

Para obtener una lista completa de cambios rupturistas (breaking changes), consulta la Guía de migración. Esta publicación se centra en las nuevas características y mejoras.

¿Por qué una nueva versión mayor?

Zod v3.0 fue lanzado en mayo de 2021 (!). En ese entonces, Zod tenía 2700 estrellas en GitHub y 600k descargas semanales. Hoy tiene 37.8k estrellas y 31M de descargas semanales (¡frente a los 23M cuando salió la beta hace 6 semanas!). Después de 24 versiones menores, el código base de Zod 3 había alcanzado un techo; las características y mejoras más solicitadas requieren cambios rupturistas.

Zod 4 soluciona una serie de limitaciones de diseño de larga data de Zod 3 de un solo golpe, allanando el camino para varias características solicitadas desde hace mucho tiempo y un gran salto en el rendimiento. Cierra 9 de los 10 problemas abiertos más votados de Zod. Con suerte, servirá como la nueva base para muchos años más por venir.

Para un desglose escaneable de lo que es nuevo, consulta la tabla de contenido. Haz clic en cualquier elemento para saltar a esa sección.

Pruebas de rendimiento (Benchmarks)

Puedes ejecutar estas pruebas tú mismo en el repositorio de Zod:

$ git clone [email protected]:colinhacks/zod.git
$ cd zod
$ git switch v4
$ pnpm install

Luego, para ejecutar una prueba en particular:

$ pnpm bench <name>

Análisis de cadenas 14x más rápido

$ pnpm bench string
runtime: node v22.13.0 (arm64-darwin)
 
benchmark      time (avg)             (min max)       p75       p99      p999
------------------------------------------------- -----------------------------
 z.string().parse
------------------------------------------------- -----------------------------
zod3          363 µs/iter       (338 µs 683 µs)    351 µs    467 µs    572 µs
zod4       24'674 ns/iter    (21'083 ns 235 µs) 24'209 ns 76'125 ns    120 µs
 
summary for z.string().parse
  zod4
   14.71x faster than zod3

Análisis de matrices 7x más rápido

$ pnpm bench array
runtime: node v22.13.0 (arm64-darwin)
 
benchmark      time (avg)             (min max)       p75       p99      p999
------------------------------------------------- -----------------------------
 z.array() parsing
------------------------------------------------- -----------------------------
zod3          147 µs/iter       (137 µs 767 µs)    140 µs    246 µs    520 µs
zod4       19'817 ns/iter    (18'125 ns 436 µs) 19'125 ns 44'500 ns    137 µs
 
summary for z.array() parsing
  zod4
   7.43x faster than zod3

Análisis de objetos 6.5x más rápido

Esto ejecuta el benchmark de bibliotecas de validación Moltar.

$ pnpm bench object-moltar
benchmark      time (avg)             (min max)       p75       p99      p999
------------------------------------------------- -----------------------------
 z.object() safeParse
------------------------------------------------- -----------------------------
zod3          805 µs/iter     (771 µs 2'802 µs)    804 µs    928 µs  2'802 µs
zod4          124 µs/iter     (118 µs 1'236 µs)    119 µs    231 µs    329 µs
 
summary for z.object() safeParse
  zod4
   6.5x faster than zod3

Reducción de 100x en instanciaciones de tsc

Considera el siguiente archivo simple:

import * as z from "zod";
 
export const A = z.object({
  a: z.string(),
  b: z.string(),
  c: z.string(),
  d: z.string(),
  e: z.string(),
});
 
export const B = A.extend({
  f: z.string(),
  g: z.string(),
  h: z.string(),
});

Compilar este archivo con tsc --extendedDiagnostics usando "zod/v3" resulta en >25000 instanciaciones de tipos. Con "zod/v4" solo resulta en ~175.

El repositorio de Zod contiene un patio de juegos de benchmarking de tsc. Pruébalo tú mismo usando los benchmarks del compilador en packages/tsc. Los números exactos pueden cambiar a medida que evoluciona la implementación.

$ cd packages/tsc
$ pnpm bench object-with-extend

Más importante aún, Zod 4 ha rediseñado y simplificado los genéricos de ZodObject y otras clases de esquema para evitar algunas "explosiones de instanciación" perniciosas. Por ejemplo, encadenar .extend() y .omit() repetidamente, algo que anteriormente causaba problemas al compilador:

import * as z from "zod";
 
export const a = z.object({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});
 
export const b = a.omit({
  a: true,
  b: true,
  c: true,
});
 
export const c = b.extend({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});
 
export const d = c.omit({
  a: true,
  b: true,
  c: true,
});
 
export const e = d.extend({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});
 
export const f = e.omit({
  a: true,
  b: true,
  c: true,
});
 
export const g = f.extend({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});
 
export const h = g.omit({
  a: true,
  b: true,
  c: true,
});
 
export const i = h.extend({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});
 
export const j = i.omit({
  a: true,
  b: true,
  c: true,
});
 
export const k = j.extend({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});
 
export const l = k.omit({
  a: true,
  b: true,
  c: true,
});
 
export const m = l.extend({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});
 
export const n = m.omit({
  a: true,
  b: true,
  c: true,
});
 
export const o = n.extend({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});
 
export const p = o.omit({
  a: true,
  b: true,
  c: true,
});
 
export const q = p.extend({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});

En Zod 3, esto tomaba 4000ms en compilar; y agregar llamadas adicionales a .extend() desencadenaría un error de "Posiblemente infinito". En Zod 4, esto compila en 400ms, 10x más rápido.

Junto con el próximo compilador tsgo, el rendimiento del editor de Zod 4 escalará a esquemas y bases de código mucho más grandes.

Reducción de 2x en el tamaño del paquete central

Considera el siguiente script simple.

import * as z from "zod";
 
const schema = z.boolean();
 
schema.parse(true);

Es tan simple como parece cuando se trata de validación. Eso es intencional; es una buena manera de medir el tamaño del paquete central—el código que terminará en el paquete incluso en casos simples. Empaquetaremos esto con rollup usando tanto Zod 3 como Zod 4 y compararemos los paquetes finales.

PaquetePaquete (gzip)
Zod 312.47kb
Zod 45.36kb

El paquete central es ~57% más pequeño en Zod 4 (2.3x). ¡Eso es bueno! Pero podemos hacerlo mucho mejor.

Presentando Zod Mini

La API de Zod, cargada de métodos, es fundamentalmente difícil de someter a tree-shaking. Incluso nuestro script simple z.boolean() extrae las implementaciones de un montón de métodos que no usamos, como .optional(), .array(), etc. Escribir implementaciones más ligeras solo te lleva hasta cierto punto. Ahí es donde entra Zod Mini.

npm install zod@^4.0.0

Es una variante de Zod con una API funcional y apta para tree-shaking que se corresponde uno a uno con zod. Donde Zod usa métodos, Zod Mini generalmente usa funciones envolventes:

import * as z from "zod/mini";
 
z.optional(z.string());
 
z.union([z.string(), z.number()]);
 
z.extend(z.object({ /* ... */ }), { age: z.number() });

¡No todos los métodos han desaparecido! Los métodos de análisis (parsing) son idénticos en Zod y Zod Mini:

import * as z from "zod/mini";
 
z.string().parse("asdf");
z.string().safeParse("asdf");
await z.string().parseAsync("asdf");
await z.string().safeParseAsync("asdf");

También hay un método .check() de propósito general utilizado para agregar refinamientos.

import * as z from "zod/mini";
 
z.array(z.number()).check(
  z.minLength(5), 
  z.maxLength(10),
  z.refine(arr => arr.includes(5))
);

Los siguientes refinamientos de nivel superior están disponibles en Zod Mini. Debería ser bastante autoexplicativo a qué métodos de Zod corresponden.

import * as z from "zod/mini";
 
// comprobaciones personalizadas
z.refine();
 
// comprobaciones de primera clase
z.lt(value);
z.lte(value); // alias: z.maximum()
z.gt(value);
z.gte(value); // alias: z.minimum()
z.positive();
z.negative();
z.nonpositive();
z.nonnegative();
z.multipleOf(value);
z.maxSize(value);
z.minSize(value);
z.size(value);
z.maxLength(value);
z.minLength(value);
z.length(value);
z.regex(regex);
z.lowercase();
z.uppercase();
z.includes(value);
z.startsWith(value);
z.endsWith(value);
z.property(key, schema); // para esquemas de objetos; verifica `input[key]` contra `schema`
z.mime(value); // para esquemas de archivos (ver más abajo)
 
// sobrescrituras (¡estas *no* cambian el tipo inferido!)
z.overwrite(value => newValue);
z.normalize();
z.trim();
z.toLowerCase();
z.toUpperCase();

Esta API más funcional hace que sea más fácil para los empaquetadores eliminar las API que no utilizas (tree-shaking). Si bien Zod regular se sigue recomendando para la mayoría de los casos de uso, cualquier proyecto con restricciones de tamaño de paquete poco comunes debería considerar Zod Mini.

Reducción de 6.6x en el tamaño del paquete central

Aquí está el script de arriba, actualizado para usar "zod/mini" en lugar de "zod".

import * as z from "zod/mini";
 
const schema = z.boolean();
schema.parse(false);

Cuando empaquetamos esto con rollup, el tamaño del paquete gzippeado es 1.88kb. Esa es una reducción del 85% (6.6x) en el tamaño del paquete central en comparación con zod@3.

PaquetePaquete (gzip)
Zod 312.47kb
Zod 4 (regular)5.36kb
Zod 4 (mini)1.88kb

Obtén más información en la página de documentación dedicada a zod/mini. Los detalles completos de la API se mezclan en las páginas de documentación existentes; los bloques de código contienen pestañas separadas para "Zod" y "Zod Mini" donde sus API divergen.

Metadatos

Zod 4 introduce un nuevo sistema para agregar metadatos fuertemente tipados a tus esquemas. Los metadatos no se almacenan dentro del esquema en sí; en su lugar, se almacenan en un "registro de esquemas" que asocia un esquema con algunos metadatos tipados. Para crear un registro con z.registry():

import * as z from "zod";
 
const myRegistry = z.registry<{ title: string; description: string }>();

Para agregar esquemas a tu registro:

const emailSchema = z.string().email();
 
myRegistry.add(emailSchema, { title: "Dirección de correo electrónico", description: "..." });
myRegistry.get(emailSchema);
// => { title: "Dirección de correo electrónico", ... }

Alternativamente, puedes usar el método .register() en un esquema para mayor comodidad:

emailSchema.register(myRegistry, { title: "Dirección de correo electrónico", description: "..." })
// => returns emailSchema

El registro global

Zod también exporta un registro global z.globalRegistry que acepta algunos metadatos comunes compatibles con JSON Schema:

z.globalRegistry.add(z.string(), { 
  id: "email_address",
  title: "Email address",
  description: "Provide your email",
  examples: ["[email protected]"],
  extraKey: "Additional properties are also allowed"
});

.meta()

Para agregar convenientemente un esquema a z.globalRegistry, usa el método .meta().

z.string().meta({ 
  id: "email_address",
  title: "Email address",
  description: "Provide your email",
  examples: ["[email protected]"],
  // ...
});

Para compatibilidad con Zod 3, .describe() todavía está disponible, pero se prefiere .meta().

z.string().describe("An email address");
 
// equivalente a
z.string().meta({ description: "An email address" });

Conversión a JSON Schema

Zod 4 introduce la conversión a JSON Schema de primera parte a través de z.toJSONSchema().

import * as z from "zod";
 
const mySchema = z.object({name: z.string(), points: z.number()});
 
z.toJSONSchema(mySchema);
// => {
//   type: "object",
//   properties: {
//     name: {type: "string"},
//     points: {type: "number"},
//   },
//   required: ["name", "points"],
// }

Cualquier metadato en z.globalRegistry se incluye automáticamente en la salida de JSON Schema.

const mySchema = z.object({
  firstName: z.string().describe("Your first name"),
  lastName: z.string().meta({ title: "last_name" }),
  age: z.number().meta({ examples: [12, 99] }),
});
 
z.toJSONSchema(mySchema);
// => {
//   type: 'object',
//   properties: {
//     firstName: { type: 'string', description: 'Your first name' },
//     lastName: { type: 'string', title: 'last_name' },
//     age: { type: 'number', examples: [ 12, 99 ] }
//   },
//   required: [ 'firstName', 'lastName', 'age' ]
// }

Consulta la documentación de JSON Schema para obtener información sobre cómo personalizar el JSON Schema generado.

Objetos recursivos

Este fue inesperado. Después de años de tratar de resolver este problema, finalmente encontré una manera de inferir correctamente los tipos de objetos recursivos en Zod. Para definir un tipo recursivo:

const Category = z.object({
  name: z.string(),
  get subcategories(){
    return z.array(Category)
  }
});
 
type Category = z.infer<typeof Category>;
// { name: string; subcategories: Category[] }

También puedes representar tipos mutuamente recursivos:

const User = z.object({
  email: z.email(),
  get posts(){
    return z.array(Post)
  }
});
 
const Post = z.object({
  title: z.string(),
  get author(){
    return User
  }
});

A diferencia del patrón de Zod 3 para tipos recursivos, no se requiere casting de tipos. Los esquemas resultantes son instancias simples de ZodObject y tienen el conjunto completo de métodos disponibles.

Post.pick({ title: true })
Post.partial();
Post.extend({ publishDate: z.date() });

Esquemas de archivo (File schemas)

Para validar instancias de File:

const fileSchema = z.file();
 
fileSchema.min(10_000); // minimum .size (bytes)
fileSchema.max(1_000_000); // maximum .size (bytes)
fileSchema.mime(["image/png"]); // MIME type

Internacionalización

Zod 4 introduce una nueva API locales para traducir globalmente los mensajes de error a diferentes idiomas.

import * as z from "zod";
 
// configure English locale (default)
z.config(z.locales.en());

Consulta la lista completa de configuraciones regionales admitidas en Personalización de errores; esta sección siempre se actualiza con una lista de idiomas admitidos a medida que están disponibles.

Impresión bonita de errores

La popularidad del paquete zod-validation-error demuestra que existe una demanda significativa de una API oficial para la impresión bonita de errores. Si estás utilizando ese paquete actualmente, por supuesto, continúa usándolo.

Zod ahora implementa una función de nivel superior z.prettifyError para convertir un ZodError en una cadena formateada fácil de usar.

const myError = new z.ZodError([
  {
    code: 'unrecognized_keys',
    keys: [ 'extraField' ],
    path: [],
    message: 'Unrecognized key: "extraField"'
  },
  {
    expected: 'string',
    code: 'invalid_type',
    path: [ 'username' ],
    message: 'Invalid input: expected string, received number'
  },
  {
    origin: 'number',
    code: 'too_small',
    minimum: 0,
    inclusive: true,
    path: [ 'favoriteNumbers', 1 ],
    message: 'Too small: expected number to be >=0'
  }
]);
 
z.prettifyError(myError);

Esto devuelve la siguiente cadena multilínea imprimible bonita:

✖ Unrecognized key: "extraField"
✖ Invalid input: expected string, received number
  → at username
✖ Invalid input: expected number, received string
  → at favoriteNumbers[1]

Actualmente el formato no es configurable; esto puede cambiar en el futuro.

Formatos de cadena de nivel superior

Todos los "formatos de cadena" (correo electrónico, etc.) han sido promovidos a funciones de nivel superior en el módulo z. Esto es a la vez más conciso y más apto para tree-shaking. Los equivalentes de método (z.string().email(), etc.) todavía están disponibles pero han quedado obsoletos. Se eliminarán en la próxima versión mayor.

z.email();
z.uuidv4();
z.uuidv7();
z.uuidv8();
z.ipv4();
z.ipv6();
z.cidrv4();
z.cidrv6();
z.url();
z.e164();
z.base64();
z.base64url();
z.jwt();
z.lowercase();
z.iso.date();
z.iso.datetime();
z.iso.duration();
z.iso.time();

Regex de correo electrónico personalizado

La API z.email() ahora admite una expresión regular personalizada. No hay una única expresión regular de correo electrónico canónica; diferentes aplicaciones pueden optar por ser más o menos estrictas. Para mayor comodidad, Zod exporta algunas comunes.

// Regex de correo electrónico predeterminado de Zod (reglas de Gmail)
// ver colinhacks.com/essays/reasonable-email-regex
z.email(); // z.regexes.email
 
// la regex utilizada por los navegadores para validar campos input[type=email]
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email
z.email({ pattern: z.regexes.html5Email });
 
// la regex clásica de emailregex.com (RFC 5322)
z.email({ pattern: z.regexes.rfc5322Email });
 
// una regex flexible que permite Unicode (buena para correos electrónicos internacionales)
z.email({ pattern: z.regexes.unicodeEmail });

Tipos de literal de plantilla (Template literal types)

Zod 4 implementa z.templateLiteral(). Los tipos de literal de plantilla son quizás la característica más grande del sistema de tipos de TypeScript que no era representable anteriormente.

const hello = z.templateLiteral(["hello, ", z.string()]);
// `hello, ${string}`
 
const cssUnits = z.enum(["px", "em", "rem", "%"]);
const css = z.templateLiteral([z.number(), cssUnits]);
// `${number}px` | `${number}em` | `${number}rem` | `${number}%`
 
const email = z.templateLiteral([
  z.string().min(1),
  "@",
  z.string().max(64),
]);
// `${string}@${string}` (¡los refinamientos min/max se aplican!)

Cada tipo de esquema de Zod que se puede convertir en cadena almacena una regex interna: cadenas, formatos de cadena como z.email(), números, booleanos, bigint, enumeraciones, literales, indefinido/opcional, nulo/anulable y otros literales de plantilla. El constructor z.templateLiteral concatena estos en una súper-regex, por lo que cosas como los formatos de cadena (z.email()) se aplican correctamente (¡pero los refinamientos personalizados no!).

Lee la documentación de literales de plantilla para más información.

Formatos numéricos (Number formats)

Se han agregado nuevos "formatos" numéricos para representar tipos de enteros y flotantes de ancho fijo. Estos devuelven una instancia de ZodNumber con las restricciones mínimas/máximas adecuadas ya agregadas.

z.int();      // [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
z.float32();  // [-3.4028234663852886e38, 3.4028234663852886e38]
z.float64();  // [-1.7976931348623157e308, 1.7976931348623157e308]
z.int32();    // [-2147483648, 2147483647]
z.uint32();   // [0, 4294967295]

Del mismo modo, también se han agregado los siguientes formatos numéricos bigint. Estos tipos de enteros exceden lo que se puede representar de forma segura mediante un number en JavaScript, por lo que devuelven una instancia de ZodBigInt con las restricciones mínimas/máximas adecuadas ya agregadas.

z.int64();    // [-9223372036854775808n, 9223372036854775807n]
z.uint64();   // [0n, 18446744073709551615n]

Stringbool

La API existente z.coerce.boolean() es muy simple: los valores falsy (false, undefined, null, 0, "", NaN, etc.) se vuelven false, los valores truthy se vuelven true.

Esta sigue siendo una buena API, y su comportamiento se alinea con las otras API z.coerce. Pero algunos usuarios solicitaron una coerción booleana más sofisticada al estilo "env". Para soportar esto, Zod 4 introduce z.stringbool():

const strbool = z.stringbool();
 
strbool.parse("true")         // => true
strbool.parse("1")            // => true
strbool.parse("yes")          // => true
strbool.parse("on")           // => true
strbool.parse("y")            // => true
strbool.parse("enabled")      // => true
 
strbool.parse("false");       // => false
strbool.parse("0");           // => false
strbool.parse("no");          // => false
strbool.parse("off");         // => false
strbool.parse("n");           // => false
strbool.parse("disabled");    // => false
 
strbool.parse(/* anything else */); // ZodError<[{ code: "invalid_value" }]>

Para personalizar los valores truthy y falsy:

z.stringbool({
  truthy: ["yes", "true"],
  falsy: ["no", "false"]
})

Consulta la documentación de z.stringbool() para obtener más información.

Personalización de errores simplificada

La mayoría de los cambios rupturistas en Zod 4 implican las API de personalización de errores. Eran un poco desordenadas en Zod 3; Zod 4 hace las cosas significativamente más elegantes, hasta el punto en que creo que vale la pena destacarlo aquí.

En resumen, ahora hay un único parámetro unificado error para personalizar errores, reemplazando las siguientes API:

Reemplaza message con error. (El parámetro message todavía es compatible pero está obsoleto).

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

Reemplaza invalid_type_error y required_error con error (sintaxis de función):

// Zod 3
- z.string({ 
-   required_error: "This field is required" 
-   invalid_type_error: "Not a string", 
- });
 
// Zod 4 
+ z.string({ error: (issue) => issue.input === undefined ? 
+  "This field is required" :
+  "Not a string" 
+ });

Reemplaza errorMap con error (sintaxis de función):

// Zod 3 
- z.string({
-   errorMap: (issue, ctx) => {
-     if (issue.code === "too_small") {
-       return { message: `Value must be >${issue.minimum}` };
-     }
-     return { message: ctx.defaultError };
-   },
- });
 
// Zod 4
+ z.string({
+   error: (issue) => {
+     if (issue.code === "too_small") {
+       return `Value must be >${issue.minimum}`
+     }
+   },
+ });

z.discriminatedUnion() mejorado

Las uniones discriminadas ahora admiten una serie de tipos de esquema que no se admitían anteriormente, incluidas uniones y tuberías (pipes):

const MyResult = z.discriminatedUnion("status", [
  // simple literal
  z.object({ status: z.literal("aaa"), data: z.string() }),
  // union discriminator
  z.object({ status: z.union([z.literal("bbb"), z.literal("ccc")]) }),
  // pipe discriminator
  z.object({ status: z.literal("fail").transform(val => val.toUpperCase()) }),
]);

Quizás lo más importante es que las uniones discriminadas ahora se componen: puedes usar una unión discriminada como miembro de otra.

const BaseError = z.object({ status: z.literal("failed"), message: z.string() });
 
const MyResult = z.discriminatedUnion("status", [
  z.object({ status: z.literal("success"), data: z.string() }),
  z.discriminatedUnion("code", [
    BaseError.extend({ code: z.literal(400) }),
    BaseError.extend({ code: z.literal(401) }),
    BaseError.extend({ code: z.literal(500) })
  ])
]);

Múltiples valores en z.literal()

La API z.literal() ahora admite opcionalmente múltiples valores.

const httpCodes = z.literal([ 200, 201, 202, 204, 206, 207, 208, 226 ]);
 
// anteriormente en Zod 3:
const httpCodes = z.union([
  z.literal(200),
  z.literal(201),
  z.literal(202),
  z.literal(204),
  z.literal(206),
  z.literal(207),
  z.literal(208),
  z.literal(226)
]);

Los refinamientos viven dentro de los esquemas

En Zod 3, se almacenaban en una clase ZodEffects que envolvía el esquema original. Esto era inconveniente, ya que significaba que no podías intercalar .refine() con otros métodos de esquema como .min().

z.string()
  .refine(val => val.includes("@"))
  .min(5);
// ^ ❌ Property 'min' does not exist on type ZodEffects<ZodString, string, string>

En Zod 4, los refinamientos se almacenan dentro de los esquemas mismos, por lo que el código anterior funciona como se espera.

z.string()
  .refine(val => val.includes("@"))
  .min(5); // ✅

.overwrite()

El método .transform() es extremadamente útil, pero tiene una gran desventaja: el tipo de salida ya no es introspectable en tiempo de ejecución. La función de transformación es una caja negra que puede devolver cualquier cosa. Esto significa (entre otras cosas) que no hay una forma segura de convertir el esquema a JSON Schema.

const Squared = z.number().transform(val => val ** 2);
// => ZodPipe<ZodNumber, ZodTransform>

Zod 4 introduce un nuevo método .overwrite() para representar transformaciones que no cambian el tipo inferido. A diferencia de .transform(), este método devuelve una instancia de la clase original. La función de sobrescritura se almacena como un refinamiento, por lo que no modifíca (y no puede modificar) el tipo inferido.

z.number().overwrite(val => val ** 2).max(100);
// => ZodNumber

Los métodos existentes .trim(), .toLowerCase() y .toUpperCase() se han reimplementado usando .overwrite().

Una base extensible: zod/v4/core

Si bien esto no será relevante para la mayoría de los usuarios de Zod, vale la pena destacarlo. La adición de Zod Mini requirió la creación de un subpaquete compartido zod/v4/core que contiene la funcionalidad central compartida entre Zod y Zod Mini.

Al principio me resistí a esto, pero ahora lo veo como una de las características más importantes de Zod 4. Permite a Zod subir de nivel de una biblioteca simple a un "sustrato" de validación rápido que se puede espolvorear en otras bibliotecas.

Si estás creando una biblioteca de esquemas, consulta las implementaciones de Zod y Zod Mini para ver cómo construir sobre la base que proporciona zod/v4/core. No dudes en ponerte en contacto en las discusiones de GitHub o a través de X/Bluesky para obtener ayuda o comentarios.

Conclusión

Planeo escribir una serie de publicaciones adicionales explicando el proceso de diseño detrás de algunas características importantes como Zod Mini. Actualizaré esta sección a medida que se publiquen.

Para los autores de bibliotecas, ahora hay una guía dedicada Para autores de bibliotecas que describe las mejores prácticas para construir sobre Zod. Responde preguntas comunes sobre cómo soportar Zod 3 y Zod 4 (incluido Mini) simultáneamente.

pnpm upgrade zod@latest

¡Feliz análisis (parsing)!
— Colin McDonnell @colinhacks