Lanzamiento de Zod 4
(zod.dev)- Zod 4 se publica como versión estable tras 1 año de desarrollo, corrigiendo los límites de diseño de Zod 3 y mejorando significativamente el rendimiento, el tamaño del bundle y la eficiencia de compilación de TypeScript
- Zod 3, lanzado en mayo de 2021, creció desde 2,700 estrellas en GitHub y 600 mil descargas semanales hasta las actuales 37.8k estrellas y 31 millones de descargas semanales; después de 24 versiones menores, las mejoras importantes ya requerían cambios incompatibles
- En los benchmarks, el parsing de strings es 14 veces más rápido, el de arrays 7 veces más rápido y el de objetos 6.5 veces más rápido; las instanciaciones de tipos en
tsctambién bajaron de más de 25,000 a unas 175 en un ejemplo simple - El tamaño del bundle principal, medido con gzip, se redujo de
12.47kben Zod 3 a5.36kben la versión normal de Zod 4, y Zod Mini, con una API funcional compatible con tree shaking, baja hasta1.88kb - Entre las nuevas funciones se incluyen conversión a JSON Schema, registros de metadatos, inferencia de tipos de objetos recursivos, internacionalización, pretty-printing de errores, tipos template literal,
z.stringbool(), un parámetroerrorunificado y mejoras enz.discriminatedUnion()
Por qué Zod 4 es una nueva versión major
- Zod 4 alcanzó la versión estable tras 1 año de desarrollo activo, con una base más rápida, más ligera y más eficiente para
tsc, sobre la que se implementaron funciones muy solicitadas desde hace tiempo - Zod v3.0 se lanzó en mayo de 2021, cuando tenía 2,700 estrellas en GitHub y 600 mil descargas semanales
- Actualmente Zod registra 37.8k estrellas en GitHub y 31 millones de descargas semanales
- Hace 6 semanas, cuando salió la beta, tenía 23 millones de descargas semanales
- Tras acumular 24 versiones menores, la base de código de Zod 3 llegó a su límite, y las funciones y mejoras más pedidas requerían cambios incompatibles
- Zod 4 cerró 9 de los 10 issues públicos con más votos
Rendimiento de parsing y eficiencia de compilación en TypeScript
- Zod 4 ofrece benchmarks que pueden ejecutarse directamente desde el repositorio
- Mejoras en rendimiento de parsing:
- Parsing de strings: 14 veces más rápido
- Parsing de arrays: 7 veces más rápido
- Parsing de objetos: 6.5 veces más rápido
- El benchmark de parsing de objetos ejecuta el Moltar validation library benchmark
- Según
tsc --extendedDiagnostics, en la compilación de un archivo simple las instanciaciones de tipos se reducen drásticamente- Usando
"zod/v3": más de 25,000 veces - Usando
"zod/v4": alrededor de 175 veces
- Usando
- Zod 4 rediseñó y simplificó los genéricos de
ZodObjecty otras clases de schema para evitar la explosión de instanciaciones - El patrón de encadenar repetidamente
.extend()y.omit()tardaba4000msen compilar en Zod 3, y agregar otra llamada a.extend()podía provocar el error"Possibly infinite" - El mismo patrón compila en
400msen Zod 4, siendo 10 veces más rápido - En el futuro, al usarse junto con el compilador
tsgo, el rendimiento del editor con Zod 4 podría escalar mejor a schemas y codebases más grandes
Tamaño del bundle y Zod Mini
- Se comparó el tamaño del bundle principal empaquetando con
rollupun script simple de validación conz.boolean() - Tamaño del bundle principal con gzip:
- Zod 3:
12.47kb - Zod 4 versión normal:
5.36kb
- Zod 3:
- El bundle principal de la versión normal de Zod 4 es aproximadamente 57% más pequeño que el de Zod 3, una reducción de 2.3 veces
- La API centrada en métodos de Zod es difícil de optimizar con tree shaking por naturaleza, y hasta un script simple con
z.boolean()arrastra implementaciones no usadas como.optional()o.array() - Zod Mini ofrece una API funcional con tree shaking que corresponde 1:1 con
zod- Donde Zod usa métodos, Zod Mini normalmente usa funciones envoltorio
- Los métodos de parsing son iguales en Zod y Zod Mini
- Se agrega refinamiento con el método genérico
.check()
- Zod normal sigue siendo la recomendación para la mayoría de los casos de uso, y los proyectos con restricciones de tamaño de bundle inusualmente estrictas pueden considerar Zod Mini
- Usando
zod/mini, el tamaño del bundle con gzip es de1.88kb- Frente a Zod 3, eso representa una reducción de 85% y 6.6 veces menos
- Tabla comparativa: Zod 3
12.47kb, Zod 4 normal5.36kb, Zod 4 Mini1.88kb
- Más detalles pueden verse en la documentación de
zod/mini
Metadatos y JSON Schema
- Zod 4 introduce un nuevo sistema para agregar metadatos fuertemente tipados a los schemas
- Los metadatos no se guardan en el schema mismo, sino en un schema registry que vincula el schema con typed metadata
- Se puede crear un registro con
z.registry(), y el método.register()del schema también permite registrarlo de forma conveniente - Zod exporta
z.globalRegistry, un registro global que recibe metadatos comunes compatibles con JSON Schema - El método
.meta()agrega de forma conveniente un schema az.globalRegistry - Por compatibilidad con Zod 3,
.describe()sigue disponible, pero en Zod 4 se recomienda.meta() - Zod 4 ofrece conversión nativa a JSON Schema mediante
z.toJSONSchema() - Los metadatos de
z.globalRegistryse incluyen automáticamente en la salida de JSON Schema - La información para personalizar el JSON Schema generado está en la documentación de JSON Schema
Nuevas expresiones de schemas y funciones de tipos
- Zod 4 puede inferir correctamente tipos de objetos recursivos
- Puede expresar tipos recursivos y tipos mutuamente recursivos
- A diferencia del patrón de tipos recursivos de Zod 3, no requiere type casting
- El schema resultante es una instancia normal de
ZodObjecty puede usar todos sus métodos
- Se añadió un File schema para validar instancias de
File z.templateLiteral()representa los tipos template literal de TypeScript- Los tipos de schema de Zod que pueden convertirse a string almacenan internamente una expresión regular
- Entre los objetivos se incluyen strings, formatos de string como
z.email(), números, boolean, bigint, enum, literal, undefined/optional, null/nullable y otros template literal - El constructor
z.templateLiterallos une en una sola super-regex - Los formatos de string como
z.email()sí se aplican correctamente, pero los refinamientos personalizados no - Más detalles en la documentación de template literal
- Se añadieron nuevos formatos numéricos para representar enteros y tipos de punto flotante de ancho fijo
- Devuelven instancias de
ZodNumbercon restricciones inclusive minimum/maximum ya aplicadas
- Devuelven instancias de
- También se añadieron formatos numéricos bigint que superan el rango representable de forma segura por
numberen JavaScript- Devuelven instancias de
ZodBigIntcon restricciones inclusive minimum/maximum ya aplicadas
- Devuelven instancias de
z.literal()ahora puede recibir opcionalmente varios valores
Errores, internacionalización y formatos de string
- Zod 4 introduce una nueva API de
localespara traducir globalmente los mensajes de error a varios idiomas - La lista completa de locales compatibles está en Customizing errors y se actualizará conforme se agreguen más idiomas soportados
- Zod implementa la función de nivel superior
z.prettifyErrorpara convertirZodErroren un string con un formato amigable para el usuario- Actualmente el formato no puede configurarse
- Puede cambiar en el futuro
- Si ya se usa
zod-validation-error, se puede seguir usando
- Todos los formatos de string, como email, fueron promovidos a funciones de nivel superior del módulo
z- Es más conciso y favorece el tree shaking
- La API equivalente basada en métodos, como
z.string().email(), sigue disponible pero está deprecated - Se eliminará en la próxima versión major
- La API
z.email()soporta expresiones regulares personalizadas- No existe una sola regex canónica para email, y cada aplicación puede elegir criterios más estrictos o más flexibles
- Zod exporta algunas regex comunes por conveniencia
Conversión de booleanos y simplificación de la personalización de errores
- La API existente
z.coerce.boolean()convierte valores falsy enfalsey valores truthy entruefalse,undefined,null,0,"",NaN, etc. se convierten enfalse- Este comportamiento es consistente con las demás APIs de
z.coerce
- Zod 4 introduce
z.stringbool()para una coerción de booleanos más sofisticada, estilo variables de entorno- Permite personalizar los valores truthy y falsy
- Más detalles en la documentación de
z.stringbool()
- La mayoría de los cambios incompatibles de Zod 4 están relacionados con la API de personalización de errores
- La personalización de errores se unificó en un solo parámetro
errormessagees reemplazado porerror- El parámetro
messagesigue siendo compatible, pero está deprecated invalid_type_erroryrequired_errorse reemplazan porerrorcon sintaxis de funciónerrorMaptambién se reemplaza porerrorcon sintaxis de función
Composición, refinamiento y transformaciones
z.discriminatedUnion()ahora soporta varios tipos de schema que antes no eran compatibles- Incluyendo union y pipe
- Un discriminated union puede usarse como miembro de otro discriminated union, por lo que ahora es componible
- En Zod 3, los refinamientos se guardaban en una clase
ZodEffectsque envolvía el schema original- Esa estructura hacía incómodo mezclar
.refine()con otros métodos de schema como.min()
- Esa estructura hacía incómodo mezclar
- En Zod 4, los refinamientos se guardan dentro del schema, lo que permite usar
.refine()junto con otros métodos de forma natural - El nuevo método
.overwrite()expresa transformaciones que no cambian el tipo inferido.transform()produce un tipo de salida que no puede introspectarse en tiempo de ejecución, y la función de transformación es una caja negra que puede devolver cualquier cosa- Por eso no hay forma segura de convertirlo a JSON Schema
.overwrite()devuelve una instancia de la clase original- La función overwrite se almacena como refinamiento y no modifica el tipo inferido
- Los métodos existentes
.trim(),.toLowerCase()y.toUpperCase()fueron reimplementados usando.overwrite()
Una base para autores de librerías
- Con la incorporación de Zod Mini, se creó el subpaquete
zod/v4/core, que contiene la funcionalidad principal compartida por Zod y Zod Mini zod/v4/coreamplía a Zod de una librería simple a una base rápida de validación que puede integrarse dentro de otras librerías- Si se quiere crear una librería de schemas, se pueden tomar como referencia las implementaciones de Zod y Zod Mini para construir sobre
zod/v4/core - Para autores de librerías se ofrece la guía For library authors
- Cubre las best practices al construir sobre Zod
- Responde preguntas frecuentes sobre cómo dar soporte simultáneo a Zod 3, Zod 4 y Mini
1 comentarios
Opiniones de Hacker News
Soy el autor, pregúntenme lo que sea. Sobre la política de versiones, dejé bastante detalladas las razones de este enfoque: https://github.com/colinhacks/zod/issues/4371
En definitiva, npm no está diseñado para manejar bien la situación en la que se encuentra Zod. Decenas o cientos de librerías importan directamente interfaces/clases de Zod y las usan en su API pública, así que cada vez que Zod sube una versión mayor, esas librerías también tienen que sacar una nueva versión mayor
Visto de forma aislada suena razonable, pero con Zod podía producirse una dolorosa avalancha de versiones para todos, y parecía que una parte importante del ecosistema quedaría fijada en v3 para siempre
Por eso, de forma similar a Go, optamos por agregar un nuevo subpath en lugar de que el paquete publique una nueva versión con cambios incompatibles. En el ecosistema TypeScript, una librería puede tener una sola peer dependency
zod@^3.25.0e importar lo que necesite desde"zod/v3"y"zod/v4"para admitir ambas versiones al mismo tiempoEntiendo que por circunstancias especiales se haya elegido una política de versiones algo rara, pero desde el punto de vista de un equipo que no tiene que preocuparse por conflictos de versiones de Zod en dependencias transitivas, me gustaría que también existiera un paquete
4.0.0Si entendí bien, hay que cambiar los imports a
"zod/v4", lo cual es un cambio muy ruidoso, y puede ser molesto tener que detectar con reglas de lint el problema de que el IDE autoimporte desde'zod'.optional()en TypeScript está incluido entre los 9 o 10 issues principales resueltos: https://github.com/colinhacks/zod/issues/635Esto ha sido el mayor dolor con Zod, pero aun así Zod es tan bueno que lo seguimos usando y aguantando
Me pregunto si consideran que esto queda fuera del alcance de Zod, o si simplemente todavía no habían tenido tiempo de implementarlo. La discusión relacionada está en https://github.com/colinhacks/zod/discussions/2134
3.25.0, suena un poco absurdoHe estado usando la versión alfa de Zod durante meses y quería simplemente modificar
package.jsonpara actualizar, pero ahora parece que tendré que revisar todo el historial de gitAgradezco muchísimo el proyecto, pero este feedback en particular parece haberse vuelto innecesariamente difícil. Me pregunto si no podrían haber publicado también 4.x junto con
3.xQue “para simplificar la migración, Zod 4 se distribuya junto con Zod 3 como parte del release
zod@3.25y se importe desde el subpath"/v4"” me parece una señal de que la gestión de dependencias de npm es un completo desastreLas peer dependencies están tan rotas que v4 tiene que fingir ser v3
Es cierto que npm no maneja bien la situación en la que está Zod. Dicho eso, Zod tiene la restricción de no recibir casi ninguna librería. Decenas o cientos de librerías importan interfaces/clases directamente desde
"zod"y las usan en sus APIs públicasEsas librerías tienen que publicar una nueva versión mayor cada vez que Zod sube una versión mayor, y en Zod eso podría provocar una avalancha de versiones dolorosa para todos. Sinceramente, parecía que una parte importante del ecosistema quedaría fijada en v3 para siempre
Por eso, de forma parecida a Go, decidimos agregar un nuevo subpath en lugar de publicar una nueva versión del paquete por cada release con incompatibilidades. En el ecosistema TypeScript, con una sola peer dependency
zod@^3.25.0se pueden admitir"zod/v3"y"zod/v4"al mismo tiempoEs decir, refactorizar cada uso uno por uno a
import ... from 'zod/v4'y, cuando terminen, subir completamente a v4De hecho, npm ofrece más vías de escape, porque si hace falta permite ejecutar varias versiones al mismo tiempo o sobrescribir selectivamente partes del grafo de dependencias transitivas
¿Qué sistema de gestión de paquetes resuelve este problema? Incluso plataformas “estables” como Maven manejan estos casos publicando versiones bajo un nuevo namespace, como cuando Apache Commons pasó de v2 a v3
Hay una pregunta que me intriga desde hace mucho. He oído que Zod podría encajar aquí, pero aun viendo la documentación no tengo muy claro cómo hacerlo
Supongamos que una API de servidor devuelve tipos más refinados de lo que puede expresar. Por ejemplo,
api/:postid/authordevuelve un User, pero puede ser un User normal o un User anónimo, así que campos comousernameolocationpodrían venir como null. En ese caso, quizá querrías representar el objeto User como una unión discriminadaLos objetos que vienen de otros endpoints también podrían necesitar conversión de tipos. A veces un User incluye
Post[], y si un Post es una publicación de un moderador, podría tener propiedades especialesAntes escribía funciones como
normalizeUser()ynormalizePost(), pero se volvía desordenado muy rápido. Como cada endpoint devolvía subconjuntos distintos de los modelos User/Post, terminé teniendo unas 5 variantes denormalizePost, y era un caos. Me pregunto cómo suele resolverse este problemaLa idea es separar objetos
successyfailedusando un campo discriminador comostatus. Si hay muchas propiedades especiales para moderadores, pero no quieres enumerarlas ni verificarlas todas, puedes definir un comportamiento de passthroughAunque varios métodos tengan esquemas distintos, si comparten una parte común puedes crear un esquema de objeto compartido y luego crear objetos que le agreguen campos adicionales. Si hay muchas posibilidades, seguirá siendo desordenado, pero si la situación ya es desordenada, al menos un validador puede validar ese desorden
Si no estás usando TypeScript full-stack, por ejemplo con un backend en Python, podrías definir las distintas formas de
Usercomo modelos y uniones de Pydantic, y luego generar el esquema OpenAPI/GraphQL y usar generación de código para crear los tipos del cliente TypeScript"user_type"a todos los tipos dentro de la unión, se vuelve más fácil manejar los casos generales y especialesPor ejemplo, si verificas
user.user_type === 'authenticated', el sistema de tipos sabe que después de eso puedes usaruser.nameLas bibliotecas TypeScript para consultas GraphQL pueden inferir dinámicamente la forma de la respuesta a partir de los campos seleccionados. Si eliges
{ user { username posts { text } } }, el tipo de respuesta tendráposts; si solo eliges{ user { username } }, la propiedadpostsdesaparece por completoEl servidor sabe qué datos envía, así que debería proporcionar esa información al cliente como metadatos. En la práctica, podría ser que un backend en Python use esquemas de Pydantic, genere automáticamente una especificación OpenAPI a partir de ellos y luego el cliente genere los tipos
Zod es mucho mejor que las alternativas que he visto, pero a veces el simple hecho de necesitar esta validación explícita se siente como un fracaso de hacia dónde llegó el desarrollo web moderno
En el desarrollo full-stack es muy frustrante tener que describir la misma forma de varias maneras. Terminas necesitando validación de entradas en JS, Swagger para definir la API, validación de entradas del lado del servidor, un ORM para que encaje con el esquema y definiciones TypeScript separadas para servidor y cliente, lo cual es tedioso
TypeScript se siente como la mejor única fuente de verdad que tenemos, pero en la práctica las herramientas que intentan hacer reflexión terminan redefiniendo cada una su propio modelo y builder. Hay al menos 5 grandes áreas donde la reflexión es central, y cada una tiene varias implementaciones
reflect-metadata, una antigua herramienta de emisión de tipos de TypeScript, proporciona algo de información de tipos en runtime, pero apunta a una especificación de decoradores y metadatos muy vieja: https://www.npmjs.com/package/reflect-metadataQuizá estemos en el inicio de una unificación. El proyecto Standard Schema parece contar con soporte de la mayoría de las bibliotecas de validación principales. Pero extenderlo a definiciones de API u herramientas ORM todavía está en una etapa muy temprana
https://github.com/standard-schema/standard-schema?tab=readm...
Si cambias algo una vez en el esquema de Zod, se propaga por toda la app junto con la verificación de tipos. El esquema de Zod termina siendo la única fuente de verdad
Si ya tienes un esquema JSON Schema/Swagger, puedes generar Zod a partir de eso. Si usas un ORM de TypeScript, casi seguro existe un generador de Zod
Sinceramente, Zod está tan extendido que para mí se volvió una definición de esquema unificada de la que pueden depender las demás partes del stack. Como defines los tipos directamente, los desarrolladores realmente los mantienen actualizados. La documentación Swagger de la empresa siempre va por detrás de los cambios
Mantener el statu quo solo está bien cuando tratas con clientes y empleadores que solo quieren el resultado y no les importa el cómo. En ese contexto, estas capas desafortunadas de complejidad al menos aumentan las horas facturables
Claro, bajo el supuesto de que Zod sea la biblioteca de definición y validación de esquemas TypeScript que eligieron
Zod 4 se ve bien, pero incluso con las mejoras más recientes, ArkType sigue siendo varias veces más rápido, por un margen de un solo dígito.
Para mantener la compatibilidad hacia atrás y la compatibilidad de sintaxis, a veces es difícil hacerlo mucho más rápido que una biblioteca que arranca completamente desde cero. En un proyecto reciente analicé todas estas herramientas y elegí ArkType en parte por eso. También me pareció que la experiencia con TypeScript era mejor.
Nosotros solo usamos Zod para validación de formularios, así que pienso: “¿por qué importa esto?”. Me pregunto si hay casos en los que la gente valida cosas como mensajes de entrada de API de alto throughput, donde el rendimiento se vuelve más importante.
Aun así, no creo que vaya a migrar desde Zod.
Felicitaciones al equipo de Zod por el nuevo lanzamiento. A riesgo de sonar demasiado negativo, me da escalofríos pensar en la cantidad de cambios incompatibles que aparecen en la guía de migración.
Si tienes un proyecto que depende mucho de Zod, parece una tarea abrumadora que requerirá bastante concentración y tiempo de los desarrolladores. Me identifico especialmente porque he mantenido en el trabajo algunos proyectos de frontend de 4 o 5 años.
Los proyectos grandes de React suelen depender de muchas bibliotecas, y cuando cada una introduce cambios grandes, más aún si falta documentación, todo se vuelve abrumador muy rápido. Es una de las partes que más detesto de trabajar con JavaScript: hacer que todo encaje y siga funcionando sin problemas se siente como una cuesta arriba interminable.
Sinceramente fue una pesadilla, y pensé que ojalá lo hubiéramos hecho en Django. Contra lo que esperaba, la migración de Tailwind 3 → 4 estuvo entre las más dolorosas.
miniseparada. Es más fácil hacer una migración gradual sin tocar el gestor de paquetes.Los usuarios a los que no les interesa la edición
minino tienen que preocuparse por esa parte. Pero las ventajas de tree shaking de la ediciónminieran tan grandes que estaban impulsando el desarrollo de alternativas, y Zod tenía que aceptarlo o decaer.Como desarrollador full-stack que opera personalmente un SaaS en producción con miles de usuarios, a veces me alegra darme cuenta de cuántos problemas desconozco por completo por haber decidido no crear una SPA ni usar frameworks de frontend en JS.
Ni siquiera se me había ocurrido que hiciera falta algo como Zod/ArkType; es la primera vez que lo oigo y no tengo ningún uso para eso.
Me sorprende la cantidad de esfuerzo, complejidad y herramientas que implica crear una SPA. Si decides no crear una SPA, toda una categoría de problemas simplemente deja de existir. En su lugar, uso el navegador como fue diseñado originalmente, con recargas de página completas y desarrollo centrado en el backend, y a veces agrego reactividad con frameworks solo de backend como Laravel Livewire.
Todo es simple, desde el control de acceso hasta la validación y la gestión de estado. La app es rápida, responsive, moderna, amigable para SEO y está sirviendo a miles de personas en producción.
Al final, en algún lugar hay un esquema. Puede estar definido en el sistema de base de datos o en los modelos del ORM. Pero en proyectos grandes, poner el esquema en el sistema de base de datos o en el ORM puede crear problemas muy difíciles, así que usamos Zod y Protobuf. Creo que Protobuf también podría reemplazarse por completo con Zod o algo parecido.
El escándalo del Post Office británico involucró un sistema de TI llamado Horizon: https://en.wikipedia.org/wiki/British_Post_Office_scandal
Al leer los detalles, creo que una validación de esquemas como Zod habría contribuido a evitar este escándalo. En el apéndice técnico de los documentos judiciales de la demanda colectiva también se menciona la ausencia de uso de esquemas: https://en.wikipedia.org/wiki/Bates_%26_Others_v_Post_Office...
Por cómo suena, ahora mismo parece que escribes un montón de
ifdefensivos. O peor aún, quizá dependes solo de la validación de formularios del lado del cliente en HTML.No soy experto, así que lo digo con cautela, pero pensé que JSON Schema podría ser una buena opción porque, al estar basado en esquemas, permite implementar validadores también en lenguajes que no son TypeScript.
https://ajv.js.org es una de esas bibliotecas de JSON Schema. Me da curiosidad cómo se compara con Zod.
Si quieres, también se puede usar como conversor a JSON. Por ejemplo, puedes escribir un esquema que reciba una cadena, valide que sea una fecha ISO y devuelva un objeto
Date.La diferencia importante está en el preprocesamiento y
refine. En Zod puedes proporcionar callbacks antes de la validación, y eso es muy útil, pero no se puede expresar en JSON. Por ejemplo, convertirMM/DD/YYYYaDD/MM/YYYYantes de validar una fecha resulta útil con más frecuencia de lo que uno pensaría.Para la validación puedes usar AJV o el propio sistema de validación de esquemas de TypeBox. Este último es mucho más rápido, pero solo admite ejecución síncrona; AJV tiene opciones de validación asíncrona, como comprobaciones contra una base de datos.
Me sorprende la reacción negativa en torno a este tema.
Probé una versión inicial de Zod v4 y me gustó la nueva API, pero me preocupaba mucho cómo sería la ruta de migración. Incluso pensaba sugerir que se publicara con un nombre de paquete completamente nuevo.
Pero el enfoque del autor es inteligente. Permite que gente como yo pueda adoptar v4 de inmediato sin tener que esperar a que se actualicen todas las dependencias.
Justo acababa de empezar a incorporar Zod en un proyecto nuevo, y no pudo haber mejor momento que este.
Por lo que se ve ahora, parece que habría tenido que cambiar muchísimas cosas para pasar a v4.