Guía de migración
Esta guía de migración tiene como objetivo enumerar los cambios disruptivos (breaking changes) en Zod 4 en orden de mayor a menor impacto. Para obtener más información sobre las mejoras de rendimiento y las nuevas características de Zod 4, lee la publicación introductoria.
Muchos de los comportamientos y APIs de Zod se han vuelto más intuitivos y cohesivos. Los cambios disruptivos descritos en este documento a menudo representan mejoras importantes en la calidad de vida para los usuarios de Zod. Recomiendo encarecidamente leer esta guía detenidamente.
Nota — Zod 3 exportaba una serie de tipos y funciones de utilidad cuasi-internos no documentados que no se consideran parte de la API pública. Los cambios en estos no se documentan aquí.
Codemod no oficial — Un codemod mantenido por la comunidad zod-v3-to-v4 está disponible.
Personalización de errores (Error customization)
Zod 4 estandariza las APIs para la personalización de errores bajo un único parámetro unificado error. Anteriormente, las APIs de personalización de errores de Zod estaban fragmentadas y eran inconsistentes. Esto se ha limpiado en Zod 4.
message obsoleto
Reemplaza message con error. El parámetro message todavía es compatible pero está obsoleto (deprecated).
elimina invalid_type_error y required_error
Los parámetros invalid_type_error / required_error han sido eliminados. Estos se agregaron apresuradamente hace años como una forma de personalizar errores que fuera menos verbosa que errorMap. Venían con todo tipo de problemas (no se pueden usar junto con errorMap) y no se alinean con los códigos de problemas reales de Zod (no existe un código de problema required).
Estos ahora se pueden representar limpiamente con el nuevo parámetro error.
elimina errorMap
Esto ha sido renombrado a error.
Los mapas de errores (error maps) ahora también pueden devolver un string simple (en lugar de {message: string}). También pueden devolver undefined, lo que le dice a Zod que ceda el control al siguiente mapa de errores en la cadena.
ZodError
actualiza los formatos de issue
Los formatos de issue se han simplificado drásticamente.
A continuación se muestra la lista de tipos de issues de Zod 3 y su equivalente en Zod 4:
Si bien ciertos tipos de issues de Zod 4 se han fusionado, eliminado o modificado, cada issue sigue siendo estructuralmente similar a su contraparte de Zod 3 (idéntico, en la mayoría de los casos). Todos los issues todavía se ajustan a la misma interfaz base que Zod 3, por lo que la mayoría de la lógica de manejo de errores común funcionará sin modificaciones.
cambia la precedencia del mapa de errores
La precedencia del mapa de errores se ha cambiado para ser más consistente. Específicamente, un mapa de errores pasado a .parse() ya no tiene prioridad sobre un mapa de errores a nivel de esquema.
obsoleto .format()
El método .format() en ZodError ha quedado obsoleto. En su lugar, usa la función de nivel superior z.treeifyError(). Lee la documentación de Formateo de errores para más información.
obsoleto .flatten()
El método .flatten() en ZodError también ha quedado obsoleto. En su lugar, usa la función de nivel superior z.treeifyError(). Lee la documentación de Formateo de errores para más información.
elimina .formErrors
Esta API era idéntica a .flatten(). Existe por razones históricas y no está documentada.
obsoleto .addIssue() y .addIssues()
Empuja directamente al array err.issues en su lugar, si es necesario.
z.number()
sin valores infinitos
POSITIVE_INFINITY y NEGATIVE_INFINITY ya no se consideran valores válidos para z.number().
.safe() ya no acepta flotantes
En Zod 3, z.number().safe() está obsoleto. Ahora se comporta de manera idéntica a .int() (ver a continuación). Es importante destacar que eso significa que ya no acepta flotantes (números decimales).
.int() acepta solo enteros seguros
La API z.number().int() ya no acepta enteros inseguros (fuera del rango de Number.MIN_SAFE_INTEGER y Number.MAX_SAFE_INTEGER). Usar enteros fuera de este rango causa errores de redondeo espontáneos. (Además: deberías cambiar a z.int().)
actualizaciones de z.string()
obsoleto .email() etc
Los formatos de cadena ahora se representan como subclases de ZodString, en lugar de simples refinamientos internos. Como tal, estas APIs se han movido al espacio de nombres de nivel superior z. Las APIs de nivel superior también son menos verbosas y más fáciles de hacer "tree-shaking".
Las formas de método (z.string().email()) todavía existen y funcionan como antes, pero ahora están obsoletas.
.uuid() más estricto
z.uuid() ahora valida los UUIDs más estrictamente contra la especificación RFC 9562/4122; específicamente, los bits de variante deben ser 10 según la especificación. Para un validador "tipo UUID" más permisivo, usa z.guid().
sin relleno en .base64url()
El relleno (padding) ya no está permitido en z.base64url() (anteriormente z.string().base64url()). Generalmente es deseable que las cadenas base64url no tengan relleno y sean seguras para URL.
elimina z.string().ip()
Esto ha sido reemplazado con métodos separados .ipv4() y .ipv6(). Usa z.union() para combinarlos si necesitas aceptar ambos.
actualiza z.string().ipv6()
La validación ahora ocurre usando el constructor new URL(), que es mucho más robusto que el antiguo enfoque de expresiones regulares. Algunos valores inválidos que pasaban la validación anteriormente pueden fallar ahora.
elimina z.string().cidr()
De manera similar, esto ha sido reemplazado con métodos separados .cidrv4() y .cidrv6(). Usa z.union() para combinarlos si necesitas aceptar ambos.
actualizaciones de z.coerce
El tipo de entrada de todos los esquemas z.coerce ahora es unknown.
actualizaciones de .default()
La aplicación de .default() ha cambiado de manera sutil. Si la entrada es undefined, ZodDefault cortocircuita el proceso de análisis y devuelve el valor predeterminado. El valor predeterminado debe ser asignable al tipo de salida.
En Zod 3, .default() esperaba un valor que coincidiera con el tipo de entrada. ZodDefault analizaría el valor predeterminado, en lugar de cortocircuitar. Como tal, el valor predeterminado debe ser asignable al tipo de entrada del esquema.
Para replicar el comportamiento antiguo, Zod implementa una nueva API .prefault(). Esto es una abreviatura de "pre-parse default" (predeterminado pre-análisis).
z.object()
valores predeterminados aplicados dentro de campos opcionales
Los valores predeterminados dentro de tus propiedades se aplican, incluso dentro de campos opcionales. Esto se alinea mejor con las expectativas y resuelve un problema de usabilidad de larga data con Zod 3. Este es un cambio sutil que puede causar roturas en rutas de código que dependen de la existencia de claves, etc.
obsoleto .strict() y .passthrough()
Estos métodos generalmente ya no son necesarios. En su lugar, usa las funciones de nivel superior z.strictObject() y z.looseObject().
Estos métodos todavía están disponibles por compatibilidad con versiones anteriores, y no se eliminarán. Se consideran heredados (legacy).
obsoleto .strip()
Esto nunca fue particularmente útil, ya que era el comportamiento predeterminado de z.object(). Para convertir un objeto estricto en uno "regular", usa z.object(A.shape).
elimina .nonstrict()
Este alias largamente obsoleto para .strip() ha sido eliminado.
elimina .deepPartial()
Esto ha estado obsoleto desde hace mucho tiempo en Zod 3 y ahora se elimina en Zod 4. No hay una alternativa directa a esta API. Había muchos problemas en su implementación, y su uso es generalmente un anti-patrón.
cambia la opcionalidad de z.unknown()
Los tipos z.unknown() y z.any() ya no están marcados como "clave opcional" en los tipos inferidos.
obsoleto .merge()
El método .merge() en ZodObject ha quedado obsoleto a favor de .extend(). El método .extend() proporciona la misma funcionalidad, evita la ambigüedad en torno a la herencia de rigurosidad (strictness), y tiene un mejor rendimiento en TypeScript.
Nota: Para un rendimiento de TypeScript aún mejor, considera usar la desestructuración de objetos en lugar de .extend(). Consulta la documentación de la API para más detalles.
z.nativeEnum() obsoleto
La función z.nativeEnum() ahora está obsoleta a favor de simplemente z.enum(). La API z.enum() se ha sobrecargado para soportar una entrada tipo enum.
Como parte de esta refactorización de ZodEnum, se han eliminado una serie de características largamente obsoletas y redundantes. Todas eran idénticas y solo existían por razones históricas.
z.array()
cambia el tipo .nonempty()
Esto ahora se comporta de manera idéntica a z.array().min(1). El tipo inferido no cambia.
El comportamiento antiguo ahora se representa mejor con z.tuple() y un argumento "rest". Esto se alinea más estrechamente con el sistema de tipos de TypeScript.
z.promise() obsoleto
Rara vez hay una razón para usar z.promise(). Si tienes una entrada que puede ser una Promise, simplemente usa await antes de analizarla con Zod.
Si estás usando z.promise para definir una función asíncrona con z.function(), eso tampoco es necesario ya; consulta la sección ZodFunction a continuación.
z.function()
El resultado de z.function() ya no es un esquema Zod. En cambio, actúa como una "fábrica de funciones" independiente para definir funciones validadas por Zod. La API también ha cambiado; defines un esquema de input y output por adelantado, en lugar de usar los métodos args() y .returns().
Si tienes una necesidad desesperada de un esquema Zod con un tipo de función, considera esta solución alternativa.
agrega .implementAsync()
Para definir una función asíncrona, usa implementAsync() en lugar de implement().
.refine()
ignora los predicados de tipo
En Zod 3, pasar un predicado de tipo como una función de refinamiento aún podía reducir el tipo de un esquema. Esto no estaba documentado pero se discutió en algunos problemas. Este ya no es el caso.
elimina ctx.path
La nueva arquitectura de análisis de Zod no evalúa ansiosamente el array path. Este fue un cambio necesario que desbloquea las dramáticas mejoras de rendimiento de Zod 4.
elimina la función como segundo argumento
La siguiente sobrecarga horrorosa ha sido eliminada.
z.ostring(), etc eliminados
Los métodos de conveniencia no documentados z.ostring(), z.onumber(), etc. han sido eliminados. Estos eran métodos abreviados para definir esquemas de cadenas opcionales.
z.literal()
elimina el soporte de symbol
Los Símbolos no se consideran valores literales, ni pueden compararse simplemente con ===. Esto fue un descuido en Zod 3.
fábricas estáticas .create() eliminadas
Anteriormente, todas las clases Zod definían un método estático .create(). Estos ahora se implementan como funciones de fábrica independientes.
z.record()
elimina el uso de un solo argumento
Antes, z.record() podía usarse con un solo argumento. Esto ya no es compatible.
mejora el soporte de enum
Los registros (Records) se han vuelto mucho más inteligentes. En Zod 3, pasar un enum a z.record() como un esquema clave resultaría en un tipo parcial.
En Zod 4, este ya no es el caso. El tipo inferido es lo que esperarías, y Zod asegura la exhaustividad; es decir, se asegura de que todas las claves enum existan en la entrada durante el análisis.
Para replicar el comportamiento antiguo con claves opcionales, usa z.partialRecord():
z.intersection()
lanza Error en conflicto de fusión
La intersección de Zod analiza la entrada contra dos esquemas, luego intenta fusionar los resultados. En Zod 3, cuando los resultados no eran fusionables, Zod lanzaba un ZodError con un problema especial "invalid_intersection_types".
En Zod 4, esto lanzará un Error regular en su lugar. La existencia de resultados no fusionables indica un problema estructural con el esquema: una intersección de dos tipos incompatibles. Por lo tanto, un error regular es más apropiado que un error de validación.
Cambios internos
El usuario típico de Zod probablemente puede ignorar todo lo que está debajo de esta línea. Estos cambios no afectan a las APIs z orientadas al usuario.
Hay demasiados cambios internos para enumerar aquí, pero algunos pueden ser relevantes para los usuarios habituales que dependen (intencionalmente o no) de ciertos detalles de implementación. Estos cambios serán de particular interés para los autores de bibliotecas que crean herramientas sobre Zod.
actualizaciones de genéricos
La estructura genérica de varias clases ha cambiado. Quizás lo más significativo es el cambio a la clase base ZodType:
El segundo genérico Def ha sido eliminado por completo. En cambio, la clase base ahora solo rastrea Output e Input. Mientras que anteriormente el valor Input predeterminado era Output, ahora predeterminado es unknown. Esto permite que las funciones genéricas que involucran z.ZodType se comporten de manera más intuitiva en muchos casos.
La necesidad de z.ZodTypeAny ha sido eliminada; solo usa z.ZodType en su lugar.
agrega z.core
Muchas funciones y tipos de utilidad se han movido al nuevo sub-paquete zod/v4/core, para facilitar el intercambio de código entre Zod y Zod Mini.
Para mayor comodidad, el contenido de zod/v4/core también se reexporta desde zod y zod/mini bajo el espacio de nombres z.core.
Consulta la documentación de Zod Core para obtener más información sobre el contenido de la sub-biblioteca principal.
mueve ._def
La propiedad ._def ahora se mueve a ._zod.def. La estructura de todas las defs internas está sujeta a cambios; esto es relevante para los autores de bibliotecas pero no se documentará exhaustivamente aquí.
elimina ZodEffects
Esto no afecta a las APIs orientadas al usuario, pero es un cambio interno que vale la pena destacar. Es parte de una reestructuración más grande de cómo Zod maneja los refinamientos.
Anteriormente, tanto los refinamientos como las transformaciones vivían dentro de una clase contenedora llamada ZodEffects. Eso significa que agregar cualquiera de los dos a un esquema envolvería el esquema original en una instancia de ZodEffects. En Zod 4, los refinamientos ahora viven dentro de los propios esquemas. Más exactamente, cada esquema contiene una matriz de "checks" (comprobaciones); el concepto de un "check" es nuevo en Zod 4 y generaliza el concepto de un refinamiento para incluir transformaciones potencialmente con efectos secundarios como z.toLowerCase().
Esto es particularmente evidente en la API Zod Mini, que depende en gran medida del método .check() para componer varias validaciones juntas.
agrega ZodTransform
Mientras tanto, las transformaciones se han movido a una clase dedicada ZodTransform. Esta clase de esquema representa una transformación de entrada; de hecho, ahora puedes definir transformaciones independientes:
Esto se usa principalmente junto con ZodPipe. El método .transform() ahora devuelve una instancia de ZodPipe.
elimina ZodPreprocess
Al igual que con .transform(), la función z.preprocess() ahora devuelve una instancia de ZodPipe en lugar de una instancia dedicada de ZodPreprocess.
elimina ZodBranded
El branding ahora se maneja con una modificación directa al tipo inferido, en lugar de una clase dedicada ZodBranded. Las APIs orientadas al usuario permanecen iguales.

