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

Definir esquemas

Para validar datos, primero debes definir un esquema. Los esquemas representan tipos, desde valores primitivos simples hasta objetos anidados complejos y arrays.

Primitivos

import * as z from "zod";
 
// primitive types
z.string();
z.number();
z.bigint();
z.boolean();
z.symbol();
z.undefined();
z.null();

Coerción

Para forzar la conversión de los datos de entrada al tipo apropiado, usa z.coerce en su lugar:

z.coerce.string();    // String(input)
z.coerce.number();    // Number(input)
z.coerce.boolean();   // Boolean(input)
z.coerce.bigint();    // BigInt(input)

La variante de coerción de estos esquemas intenta convertir el valor de entrada al tipo apropiado.

const schema = z.coerce.string();
 
schema.parse("tuna");    // => "tuna"
schema.parse(42);        // => "42"
schema.parse(true);      // => "true"
schema.parse(null);      // => "null"

El tipo de entrada de estos esquemas de coerción es unknown por defecto. Para especificar un tipo de entrada más específico, pasa un parámetro genérico:

const A = z.coerce.number();
type AInput = z.input<typeof A>; // => unknown
 
const B = z.coerce.number<number>();
type BInput = z.input<typeof B>; // => number

Literales

Los esquemas literales representan un tipo literal, como "hello world" o 5.

const tuna = z.literal("tuna");
const twelve = z.literal(12);
const twobig = z.literal(2n);
const tru = z.literal(true);

Para representar los literales de JavaScript null y undefined:

z.null();
z.undefined();
z.void(); // equivalente a z.undefined()

Para permitir múltiples valores literales:

const colors = z.literal(["red", "green", "blue"]);
 
colors.parse("green"); // ✅
colors.parse("yellow"); // ❌

Para extraer el conjunto de valores permitidos de un esquema literal:

colors.values; // => Set<"red" | "green" | "blue">

Cadenas de texto (Strings)

Zod proporciona varias validaciones y transformaciones de cadenas integradas. Para realizar algunas validaciones de cadenas comunes:

z.string().max(5);
z.string().min(5);
z.string().length(5);
z.string().regex(/^[a-z]+$/);
z.string().startsWith("aaa");
z.string().endsWith("zzz");
z.string().includes("---");
z.string().uppercase();
z.string().lowercase();

Para realizar algunas transformaciones de cadenas simples:

z.string().trim(); // trim whitespace
z.string().toLowerCase(); // toLowerCase
z.string().toUpperCase(); // toUpperCase
z.string().normalize(); // normalize unicode characters

Formatos de cadena

Para validar contra algunos formatos de cadena comunes:

z.email();
z.uuid();
z.url();
z.httpUrl();       // http or https URLs only
z.hostname();
z.emoji();         // validates a single emoji character
z.base64();
z.base64url();
z.hex();
z.jwt();
z.nanoid();
z.cuid();
z.cuid2();
z.ulid();
z.ipv4();
z.ipv6();
z.mac();
z.cidrv4();        // ipv4 CIDR block
z.cidrv6();        // ipv6 CIDR block
z.hash("sha256");  // or "sha1", "sha384", "sha512", "md5"
z.iso.date();
z.iso.time();
z.iso.datetime();
z.iso.duration();

Correos electrónicos (Emails)

Para validar direcciones de correo electrónico:

z.email();

Por defecto, Zod usa una expresión regular de correo electrónico comparativamente estricta diseñada para validar direcciones de correo electrónico normales que contienen caracteres comunes. Es aproximadamente equivalente a las reglas impuestas por Gmail. Para obtener más información sobre esta expresión regular, consulta esta publicación.

/^(?!\.)(?!.*\.\.)([a-z0-9_'+\-\.]*)[a-z0-9_+-]@([a-z0-9][a-z0-9\-]*\.)+[a-z]{2,}$/i

Para personalizar el comportamiento de validación de correo electrónico, puedes pasar una expresión regular personalizada al parámetro pattern.

z.email({ pattern: /your regex here/ });

Zod exporta varias expresiones regulares útiles que podrías usar.

// Zod's default email regex
z.email();
z.email({ pattern: z.regexes.email }); // equivalent
 
// the regex used by browsers to validate input[type=email] fields
// https://developer.mozilla.org/es/docs/Web/HTML/Element/input/email
z.email({ pattern: z.regexes.html5Email });
 
// the classic emailregex.com regex (RFC 5322)
z.email({ pattern: z.regexes.rfc5322Email });
 
// a loose regex that allows Unicode (good for intl emails)
z.email({ pattern: z.regexes.unicodeEmail });

UUIDs

Para validar UUIDs:

z.uuid();

Para especificar una versión particular de UUID:

// supports "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8"
z.uuid({ version: "v4" });
 
// for convenience
z.uuidv4();
z.uuidv6();
z.uuidv7();

La especificación UUID RFC 9562/4122 requiere que los dos primeros bits del byte 8 sean 10. Otros identificadores tipo UUID no imponen esta restricción. Para validar cualquier identificador tipo UUID:

z.guid();

URLs

Para validar cualquier URL compatible con WHATWG:

const schema = z.url();
 
schema.parse("https://example.com"); // ✅
schema.parse("http://localhost"); // ✅
schema.parse("mailto:[email protected]"); // ✅

Como puedes ver, esto es bastante permisivo. Internamente, esto usa el constructor new URL() para validar las entradas; este comportamiento puede diferir entre plataformas y entornos de ejecución, pero es la forma más rigurosa de validar URIs/URLs en cualquier entorno de ejecución/motor JS dado.

Para validar el nombre de host contra una expresión regular específica:

const schema = z.url({ hostname: /^example\.com$/ });
 
schema.parse("https://example.com"); // ✅
schema.parse("https://zombo.com"); // ❌

Para validar el protocolo contra una expresión regular específica, usa el parámetro protocol.

const schema = z.url({ protocol: /^https$/ });
 
schema.parse("https://example.com"); // ✅
schema.parse("http://example.com"); // ❌

URLs Web — En muchos casos, querrás validar URLs Web específicamente. Aquí está el esquema recomendado para hacerlo:

const httpUrl = z.url({
  protocol: /^https?$/,
  hostname: z.regexes.domain
});

Esto restringe el protocolo a http/https y asegura que el nombre de host es un nombre de dominio válido con la expresión regular z.regexes.domain:

/^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/

Para normalizar URLs, usa la bandera normalize. Esto sobrescribirá el valor de entrada con la URL normalizada devuelta por new URL().

new URL("HTTP://ExAmPle.com:80/./a/../b?X=1#f oo").href
// => "http://example.com/b?X=1#f%20oo"

Fechas y horas ISO (ISO datetimes)

Como habrás notado, z.string incluye algunas validaciones relacionadas con fecha/hora. Estas validaciones se basan en expresiones regulares, por lo que no son tan estrictas como una biblioteca completa de fecha/hora. Sin embargo, son muy convenientes para validar la entrada del usuario.

El método z.iso.datetime() impone ISO 8601; por defecto, no se permiten desplazamientos (offsets) de zona horaria:

const datetime = z.iso.datetime();
 
datetime.parse("2020-01-01T06:15:00Z"); // ✅
datetime.parse("2020-01-01T06:15:00.123Z"); // ✅
datetime.parse("2020-01-01T06:15:00.123456Z"); // ✅ (arbitrary precision)
datetime.parse("2020-01-01T06:15:00+02:00"); // ❌ (offsets not allowed)
datetime.parse("2020-01-01T06:15:00"); // ❌ (local not allowed)

Para permitir desplazamientos de zona horaria:

const datetime = z.iso.datetime({ offset: true });
 
// allows timezone offsets
datetime.parse("2020-01-01T06:15:00+02:00"); // ✅
 
// basic offsets not allowed
datetime.parse("2020-01-01T06:15:00+02");    // ❌
datetime.parse("2020-01-01T06:15:00+0200");  // ❌
 
// Z is still supported
datetime.parse("2020-01-01T06:15:00Z"); // ✅ 

Para permitir fechas y horas no cualificadas (sin zona horaria):

const schema = z.iso.datetime({ local: true });
schema.parse("2020-01-01T06:15:01"); // ✅
schema.parse("2020-01-01T06:15"); // ✅ seconds optional

Para restringir la precision (precisión) de tiempo permitida. Por defecto, los segundos son opcionales y se permite una precisión de sub-segundos arbitraria.

const a = z.iso.datetime();
a.parse("2020-01-01T06:15Z"); // ✅
a.parse("2020-01-01T06:15:00Z"); // ✅
a.parse("2020-01-01T06:15:00.123Z"); // ✅
 
const b = z.iso.datetime({ precision: -1 }); // minute precision (no seconds)
b.parse("2020-01-01T06:15Z"); // ✅
b.parse("2020-01-01T06:15:00Z"); // ❌
b.parse("2020-01-01T06:15:00.123Z"); // ❌
 
const c = z.iso.datetime({ precision: 0 }); // second precision only
c.parse("2020-01-01T06:15Z"); // ❌
c.parse("2020-01-01T06:15:00Z"); // ✅
c.parse("2020-01-01T06:15:00.123Z"); // ❌
 
const d = z.iso.datetime({ precision: 3 }); // millisecond precision only
d.parse("2020-01-01T06:15Z"); // ❌
d.parse("2020-01-01T06:15:00Z"); // ❌
d.parse("2020-01-01T06:15:00.123Z"); // ✅

Fechas ISO (ISO dates)

El método z.iso.date() valida cadenas en el formato YYYY-MM-DD.

const date = z.iso.date();
 
date.parse("2020-01-01"); // ✅
date.parse("2020-1-1"); // ❌
date.parse("2020-01-32"); // ❌

Horas ISO (ISO times)

El método z.iso.time() valida cadenas en el formato HH:MM[:SS[.s+]]. Por defecto, los segundos son opcionales, así como los decimales de sub-segundos.

const time = z.iso.time();
 
time.parse("03:15"); // ✅
time.parse("03:15:00"); // ✅
time.parse("03:15:00.9999999"); // ✅ (arbitrary precision)

No se permiten desplazamientos (offsets) de ningún tipo.

time.parse("03:15:00Z"); // ❌ (no `Z` allowed)
time.parse("03:15:00+02:00"); // ❌ (no offsets allowed)

Usa el parámetro precision para restringir la precisión decimal permitida.

z.iso.time({ precision: -1 }); // HH:MM (minute precision)
z.iso.time({ precision: 0 });  // HH:MM:SS (second precision)
z.iso.time({ precision: 1 });  // HH:MM:SS.s (decisecond precision)
z.iso.time({ precision: 2 });  // HH:MM:SS.ss (centisecond precision)
z.iso.time({ precision: 3 });  // HH:MM:SS.sss (millisecond precision)

Direcciones IP

const ipv4 = z.ipv4();
ipv4.parse("192.168.0.0"); // ✅
 
const ipv6 = z.ipv6();
ipv6.parse("2001:db8:85a3::8a2e:370:7334"); // ✅

Bloques de IP (CIDR)

Valida rangos de direcciones IP especificados con notación CIDR.

const cidrv4 = z.cidrv4();
cidrv4.parse("192.168.0.0/24"); // ✅
 
const cidrv6 = z.cidrv6();
cidrv6.parse("2001:db8::/32"); // ✅

Direcciones MAC

Valida direcciones MAC estándar de 48 bits IEEE 802.

const mac = z.mac(); 
mac.parse("00:1A:2B:3C:4D:5E");  // ✅
mac.parse("00-1a-2b-3c-4d-5e");  // ❌ colon-delimited by default
mac.parse("001A:2B3C:4D5E");     // ❌ standard formats only
mac.parse("00:1A:2b:3C:4d:5E");  // ❌ no mixed case
 
// custom delimiter
const dashMac = z.mac({ delimiter: "-" });
dashMac.parse("00-1A-2B-3C-4D-5E"); // ✅

JWTs

Valida JSON Web Tokens.

z.jwt();
z.jwt({ alg: "HS256" });

Hashes

Para validar valores hash criptográficos:

z.hash("md5");
z.hash("sha1");
z.hash("sha256");
z.hash("sha384");
z.hash("sha512");

Por defecto, z.hash() espera codificación hexadecimal, como es convencional. Puedes especificar una codificación diferente con el parámetro enc:

z.hash("sha256", { enc: "hex" });       // default
z.hash("sha256", { enc: "base64" });    // base64 encoding
z.hash("sha256", { enc: "base64url" }); // base64url encoding (no padding)

Formatos personalizados

Para definir tus propios formatos de cadena:

const coolId = z.stringFormat("cool-id", ()=>{
  // arbitrary validation here
  return val.length === 100 && val.startsWith("cool-");
});
 
// a regex is also accepted
z.stringFormat("cool-id", /^cool-[a-z0-9]{95}$/);

Este esquema producirá problemas de tipo "invalid_format", que son más descriptivos que los errores "custom" producidos por refinements o z.custom().

myFormat.parse("invalid input!");
// ZodError: [
//   {
//     "code": "invalid_format",
//     "format": "cool-id",
//     "path": [],
//     "message": "Invalid cool-id"
//   }
// ]

Plantillas literales (Template literals)

New — Introducido en [email protected].

Para definir un esquema de plantilla literal:

const schema = z.templateLiteral([ "hello, ", z.string(), "!" ]);
// `hello, ${string}!`

La API z.templateLiteral puede manejar cualquier número de literales de cadena (ej. "hello") y esquemas. Se puede pasar cualquier esquema con un tipo inferido que sea asignable a string | number | bigint | boolean | null | undefined.

z.templateLiteral([ "hi there" ]);
// `hi there`
 
z.templateLiteral([ "email: ", z.string() ]);
// `email: ${string}`
 
z.templateLiteral([ "high", z.literal(5) ]);
// `high5`
 
z.templateLiteral([ z.nullable(z.literal("grassy")) ]);
// `grassy` | `null`
 
z.templateLiteral([ z.number(), z.enum(["px", "em", "rem"]) ]);
// `${number}px` | `${number}em` | `${number}rem`

Números (Numbers)

Usa z.number() para validar números. Permite cualquier número finito.

const schema = z.number();
 
schema.parse(3.14);      // ✅
schema.parse(NaN);       // ❌
schema.parse(Infinity);  // ❌

Zod implementa varias validaciones específicas para números:

z.number().gt(5);
z.number().gte(5);                     // alias .min(5)
z.number().lt(5);
z.number().lte(5);                     // alias .max(5)
z.number().positive();       
z.number().nonnegative();    
z.number().negative(); 
z.number().nonpositive(); 
z.number().multipleOf(5);              // alias .step(5)

Si (por alguna razón) quieres validar NaN, usa z.nan().

z.nan().parse(NaN);              // ✅
z.nan().parse("anything else");  // ❌

Enteros (Integers)

Para validar enteros:

z.int();     // restricts to safe integer range
z.int32();   // restrict to int32 range

BigInts

Para validar BigInts:

z.bigint();

Zod incluye varias validaciones específicas para bigint.

z.bigint().gt(5n);
z.bigint().gte(5n);                    // alias `.min(5n)`
z.bigint().lt(5n);
z.bigint().lte(5n);                    // alias `.max(5n)`
z.bigint().positive(); 
z.bigint().nonnegative(); 
z.bigint().negative(); 
z.bigint().nonpositive(); 
z.bigint().multipleOf(5n);             // alias `.step(5n)`

Booleanos (Booleans)

Para validar valores booleanos:

z.boolean().parse(true); // => true
z.boolean().parse(false); // => false

Fechas (Dates)

Usa z.date() para validar instancias de Date.

z.date().safeParse(new Date()); // success: true
z.date().safeParse("2022-01-12T06:15:00.000Z"); // success: false

Para personalizar el mensaje de error:

z.date({
  error: issue => issue.input === undefined ? "Required" : "Invalid date"
});

Zod proporciona varias validaciones específicas para fechas.

z.date().min(new Date("1900-01-01"), { error: "Too old!" });
z.date().max(new Date(), { error: "Too young!" });

Enumeraciones (Enums)

Usa z.enum para validar entradas contra un conjunto fijo de valores de cadena permitidos.

const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
 
FishEnum.parse("Salmon"); // => "Salmon"
FishEnum.parse("Swordfish"); // => ❌

Cuidado — Si declaras tu array de cadenas como una variable, Zod no podrá inferir correctamente los valores exactos de cada elemento.

const fish = ["Salmon", "Tuna", "Trout"];
 
const FishEnum = z.enum(fish);
type FishEnum = z.infer<typeof FishEnum>; // string

Para solucionar esto, pasa siempre el array directamente a la función z.enum(), o usa as const.

const fish = ["Salmon", "Tuna", "Trout"] as const;
 
const FishEnum = z.enum(fish);
type FishEnum = z.infer<typeof FishEnum>; // "Salmon" | "Tuna" | "Trout"

Se admiten literales de objeto similares a Enum ({ [key: string]: string | number }).

const Fish = {
  Salmon: 0,
  Tuna: 1
} as const
 
const FishEnum = z.enum(Fish)
FishEnum.parse(Fish.Salmon); // => ✅
FishEnum.parse(0); // => ✅
FishEnum.parse(2); // => ❌

También puedes pasar un enum de TypeScript declarado externamente.

enum Fish {
  Salmon = 0,
  Tuna = 1
}
 
const FishEnum = z.enum(Fish);
FishEnum.parse(Fish.Salmon); // => ✅
FishEnum.parse(0); // => ✅
FishEnum.parse(2); // => ❌

Zod 4 — Esto reemplaza la API z.nativeEnum() en Zod 3.

Ten en cuenta que el uso de la palabra clave enum de TypeScript no se recomienda.

enum Fish {
  Salmon = "Salmon",
  Tuna = "Tuna",
  Trout = "Trout",
}
 
const FishEnum = z.enum(Fish);

.enum

Para extraer los valores del esquema como un objeto similar a un enum:

const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
 
FishEnum.enum;
// => { Salmon: "Salmon", Tuna: "Tuna", Trout: "Trout" }

.exclude()

Para crear un nuevo esquema de enumeración, excluyendo ciertos valores:

const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
const TunaOnly = FishEnum.exclude(["Salmon", "Trout"]);

.extract()

Para crear un nuevo esquema de enumeración, extrayendo ciertos valores:

const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
const SalmonAndTroutOnly = FishEnum.extract(["Salmon", "Trout"]);

Stringbools

💎 New in Zod 4

En algunos casos (por ejemplo, al analizar variables de entorno), es valioso analizar ciertos valores de cadena 'tipo booleano' a un valor boolean plano. Para admitir 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 verdaderos (truthy) y falsos (falsy):

// these are the defaults
z.stringbool({
  truthy: ["true", "1", "yes", "on", "y", "enabled"],
  falsy: ["false", "0", "no", "off", "n", "disabled"],
});

Por defecto, el esquema no distingue entre mayúsculas y minúsculas (case-insensitive); todas las entradas se convierten a minúsculas antes de compararlas con los valores truthy/falsy. Para que distinga entre mayúsculas y minúsculas:

z.stringbool({
  case: "sensitive"
});

Opcionales (Optionals)

Para hacer que un esquema sea opcional (es decir, para permitir entradas undefined).

z.optional(z.literal("yoda")); // or z.literal("yoda").optional()

Esto devuelve una instancia de ZodOptional que envuelve el esquema original. Para extraer el esquema interno:

optionalYoda.unwrap(); // ZodLiteral<"yoda">

Anulables (Nullables)

Para hacer que un esquema sea anulable (es decir, para permitir entradas null).

z.nullable(z.literal("yoda")); // or z.literal("yoda").nullable()

Esto devuelve una instancia de ZodNullable que envuelve el esquema original. Para extraer el esquema interno:

nullableYoda.unwrap(); // ZodLiteral<"yoda">

Nullish

Para hacer que un esquema sea nullish (tanto opcional como anulable):

const nullishYoda = z.nullish(z.literal("yoda"));

Consulta el manual de TypeScript para obtener más información sobre el concepto de nullish.

Unknown

Zod tiene como objetivo reflejar el sistema de tipos de TypeScript uno a uno. Como tal, Zod proporciona APIs para representar los siguientes tipos especiales:

// allows any values
z.any(); // inferred type: `any`
z.unknown(); // inferred type: `unknown`

Never

Ningún valor pasará la validación.

z.never(); // inferred type: `never`

Objetos (Objects)

Para definir un tipo de objeto:

  // all properties are required by default
  const Person = z.object({
    name: z.string(),
    age: z.number(),
  });
 
  type Person = z.infer<typeof Person>;
  // => { name: string; age: number; }

Por defecto, todas las propiedades son obligatorias. Para hacer que ciertas propiedades sean opcionales:

const Dog = z.object({
  name: z.string(),
  age: z.number().optional(),
});
 
Dog.parse({ name: "Yeller" }); // ✅

Por defecto, las claves no reconocidas se eliminan del resultado analizado:

Dog.parse({ name: "Yeller", extraKey: true });
// => { name: "Yeller" }

z.strictObject

Para definir un esquema estricto que lanza un error cuando se encuentran claves desconocidas:

const StrictDog = z.strictObject({
  name: z.string(),
  });
 
StrictDog.parse({ name: "Yeller", extraKey: true });
// ❌ throws

z.looseObject

Para definir un esquema laxo (loose) que permite que pasen claves desconocidas:

const LooseDog = z.looseObject({
  name: z.string(),
});
 
LooseDog.parse({ name: "Yeller", extraKey: true });
// => { name: "Yeller", extraKey: true }

.catchall()

Para definir un esquema catchall que se usará para validar cualquier clave no reconocida:

const DogWithStrings = z.object({
  name: z.string(),
  age: z.number().optional(),
}).catchall(z.string());
 
DogWithStrings.parse({ name: "Yeller", extraKey: "extraValue" }); // ✅
DogWithStrings.parse({ name: "Yeller", extraKey: 42 }); // ❌

.shape

Para acceder a los esquemas internos:

Dog.shape.name; // => string schema
Dog.shape.age; // => number schema

.keyof()

Para crear un esquema ZodEnum a partir de las claves de un esquema de objeto:

const keySchema = Dog.keyof();
// => ZodEnum<["name", "age"]>

.extend()

Para agregar campos adicionales a un esquema de objeto:

const DogWithBreed = Dog.extend({
  breed: z.string(),
});

¡Esta API puede usarse para sobrescribir campos existentes! ¡Ten cuidado con este poder! Si los dos esquemas comparten claves, B anulará a A.

Alternativa: sintaxis de propagación (spread) — Alternativamente, puedes evitar .extend() por completo creando un nuevo esquema de objeto completamente. Esto hace que el nivel de rigor del esquema resultante sea visualmente obvio.

const DogWithBreed = z.object({ // or z.strictObject() or z.looseObject()...
  ...Dog.shape,
  breed: z.string(),
});

También puedes usar esto para fusionar varios objetos de una sola vez.

const DogWithBreed = z.object({
  ...Animal.shape,
  ...Pet.shape,
  breed: z.string(),
});

Este enfoque tiene algunas ventajas:

  1. Usa características a nivel de lenguaje (sintaxis de propagación (spread)) en lugar de APIs específicas de la biblioteca
  2. La misma sintaxis funciona en Zod y Zod Mini
  3. Es más eficiente para tsc: el método .extend() puede ser costoso en esquemas grandes y debido a una limitación de TypeScript se vuelve cuadráticamente más costoso cuando las llamadas se encadenan
  4. Si lo deseas, puedes cambiar el nivel de rigor del esquema resultante usando z.strictObject() o z.looseObject()

.safeExtend()

El método .safeExtend() funciona de manera similar a .extend(), pero no le permitirá sobrescribir una propiedad existente con un esquema no asignable. En otras palabras, el resultado de .safeExtend() tendrá un tipo inferido que extiende el original (en el sentido de TypeScript).

z.object({ a: z.string() }).safeExtend({ a: z.string().min(5) }); // ✅
z.object({ a: z.string() }).safeExtend({ a: z.any() }); // ✅
z.object({ a: z.string() }).safeExtend({ a: z.number() });
//                                       ^  ❌ ZodNumber is not assignable 

Usa .safeExtend() para extender esquemas que contienen refinamientos. (El .extend() normal lanzará un error cuando se use en esquemas con refinamientos).

const Base = z.object({
  a: z.string(),
  b: z.string()
}).refine(user => user.a === user.b);
 
// Extended inherits the refinements of Base
const Extended = Base.safeExtend({
  a: z.string().min(10)
});

.pick()

Inspirado por los tipos de utilidad Pick y Omit integrados en TypeScript, Zod proporciona APIs dedicadas para seleccionar (pick) y omitir (omit) ciertas claves de un esquema de objeto.

Partiendo de este esquema inicial:

const Recipe = z.object({
  title: z.string(),
  description: z.string().optional(),
  ingredients: z.array(z.string()),
});
// { title: string; description?: string | undefined; ingredients: string[] }

Para seleccionar ciertas claves:

const JustTheTitle = Recipe.pick({ title: true });

.omit()

Para omitir ciertas claves:

const RecipeNoId = Recipe.omit({ id: true });

.partial()

Por conveniencia, Zod proporciona una API dedicada para hacer que algunas o todas las propiedades sean opcionales, inspirada en el tipo de utilidad incorporado de TypeScript Partial.

Para hacer que todos los campos sean opcionales:

const PartialRecipe = Recipe.partial();
// { title?: string | undefined; description?: string | undefined; ingredients?: string[] | undefined }

Para hacer que ciertas propiedades sean opcionales:

const RecipeOptionalIngredients = Recipe.partial({
  ingredients: true,
});
// { title: string; description?: string | undefined; ingredients?: string[] | undefined }

.required()

Zod proporciona una API para hacer que algunas o todas las propiedades sean obligatorias, inspirada en el tipo de utilidad de TypeScript Required.

Para hacer que todas las propiedades sean obligatorias:

const RequiredRecipe = Recipe.required();
// { title: string; description: string; ingredients: string[] }

Para hacer que ciertas propiedades sean obligatorias:

const RecipeRequiredDescription = Recipe.required({description: true});
// { title: string; description: string; ingredients: string[] }

Objetos recursivos (Recursive objects)

Para definir un tipo autorreferencial, usa un getter en la clave. Esto permite que JavaScript resuelva el esquema cíclico en tiempo de ejecución.

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

Aunque se admiten esquemas recursivos, pasar datos cíclicos a Zod causará un bucle infinito.

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
  }
});

Todas las APIs de objetos (.pick(), .omit(), .required(), .partial(), etc.) funcionan como esperarías.

Errores de circularidad

Debido a las limitaciones de TypeScript, la inferencia de tipos recursivos puede ser delicada y solo funciona en ciertos escenarios. Algunos tipos más complicados pueden desencadenar errores de tipo recursivo como este:

const Activity = z.object({
  name: z.string(),
  get subactivities() {
    // ^ ❌ 'subactivities' implicitly has return type 'any' because it does not
    // have a return type annotation and is referenced directly or indirectly
    // in one of its return expressions.ts(7023)
 
    return z.nullable(z.array(Activity));
  },
});

En estos casos, puedes resolver el error con una anotación de tipo en el getter problemático:

const Activity = z.object({
  name: z.string(),
  get subactivities(): z.ZodNullable<z.ZodArray<typeof Activity>> {
    return z.nullable(z.array(Activity));
  },
});

Arrays

Para definir un esquema de array:

const stringArray = z.array(z.string()); // or z.string().array()

Para acceder al esquema interno de un elemento del array.

stringArray.unwrap(); // => string schema

Zod implementa varias validaciones específicas para arrays:

z.array(z.string()).min(5); // must contain 5 or more items
z.array(z.string()).max(5); // must contain 5 or fewer items
z.array(z.string()).length(5); // must contain 5 items exactly

Tuplas (Tuples)

A diferencia de los arrays, las tuplas suelen ser arrays de longitud fija que especifican diferentes esquemas para cada índice.

const MyTuple = z.tuple([
  z.string(),
  z.number(),
  z.boolean()
]);
 
type MyTuple = z.infer<typeof MyTuple>;
// [string, number, boolean]

Para agregar un argumento variádico ("rest"):

const variadicTuple = z.tuple([z.string()], z.number());
// => [string, ...number[]];

Uniones (Unions)

Los tipos de unión (A | B) representan un "O" (OR) lógico. Los esquemas de unión de Zod comprobarán la entrada con cada opción en orden. Se devuelve el primer valor que se valida con éxito.

const stringOrNumber = z.union([z.string(), z.number()]);
// string | number
 
stringOrNumber.parse("foo"); // passes
stringOrNumber.parse(14); // passes

Para extraer los esquemas de opciones internos:

stringOrNumber.options; // [ZodString, ZodNumber]

Uniones discriminadas (Discriminated unions)

Una unión discriminada es un tipo especial de unión en la que a) todas las opciones son esquemas de objeto que b) comparten una clave particular (el "discriminador"). Basándose en el valor de la clave discriminadora, TypeScript es capaz de "estrechar" (narrow) la firma de tipo como esperarías.

type MyResult =
  | { status: "success"; data: string }
  | { status: "failed"; error: string };
 
function handleResult(result: MyResult){
  if(result.status === "success"){
    result.data; // string
  } else {
    result.error; // string
  }
}

Podrías representarlo con un z.union() regular. Pero las uniones regulares son ingenuas: verifican la entrada contra cada opción en orden y devuelven la primera que pasa. Esto puede ser lento para uniones grandes.

Así que Zod proporciona una API z.discriminatedUnion() que usa una clave discriminadora para hacer el análisis más eficiente.

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

Cada opción debe ser un esquema de objeto cuya propiedad discriminadora (status en el ejemplo anterior) corresponde a algún valor literal o conjunto de valores, generalmente z.enum(), z.literal(), z.null(), o z.undefined().

Intersecciones (Intersections)

Los tipos de intersección (A & B) representan un "Y" (AND) lógico.

const a = z.union([z.number(), z.string()]);
const b = z.union([z.number(), z.boolean()]);
const c = z.intersection(a, b);
 
type c = z.infer<typeof c>; // => number

Esto puede ser útil para intersectar dos tipos de objetos.

const Person = z.object({ name: z.string() });
type Person = z.infer<typeof Person>;
 
const Employee = z.object({ role: z.string() });
type Employee = z.infer<typeof Employee>;
 
const EmployedPerson = z.intersection(Person, Employee);
type EmployedPerson = z.infer<typeof EmployedPerson>;
// Person & Employee

Al fusionar esquemas de objetos, prefiere A.extend(B) sobre las intersecciones. Usar .extend() te dará un nuevo esquema de objeto, mientras que z.intersection(A, B) devuelve una instancia ZodIntersection que carece de métodos de objeto comunes como pick y omit.

Registros (Records)

Los esquemas de registro se utilizan para validar tipos como Record<string, string>.

const IdCache = z.record(z.string(), z.string());
type IdCache = z.infer<typeof IdCache>; // Record<string, string>
 
IdCache.parse({
  carlotta: "77d2586b-9e8e-4ecf-8b21-ea7e0530eadd",
  jimmie: "77d2586b-9e8e-4ecf-8b21-ea7e0530eadd",
});

El esquema de clave puede ser cualquier esquema de Zod que sea asignable a string | number | symbol.

const Keys = z.union([z.string(), z.number(), z.symbol()]);
const AnyObject = z.record(Keys, z.unknown());
// Record<string | number | symbol, unknown>

Para crear esquemas de objetos que contengan claves definidas por un enum:

const Keys = z.enum(["id", "name", "email"]);
const Person = z.record(Keys, z.string());
// { id: string; name: string; email: string }

Zod 4 — En Zod 4, si pasas un z.enum como primer argumento a z.record(), Zod verificará exhaustivamente que todos los valores del enum existan en la entrada como claves. Este comportamiento concuerda con TypeScript:

type MyRecord = Record<"a" | "b", string>;
const myRecord: MyRecord = { a: "foo", b: "bar" }; // ✅
const myRecord: MyRecord = { a: "foo" }; // ❌ missing required key `b`

En Zod 3, no se verificaba la exhaustividad. Para replicar el comportamiento anterior, usa z.partialRecord().

Si deseas un tipo de registro parcial, usa z.partialRecord(). Esto omite las verificaciones especiales de exhaustividad que Zod ejecuta normalmente con esquemas de clave z.enum() y z.literal().

const Keys = z.enum(["id", "name", "email"]).or(z.never()); 
const Person = z.partialRecord(Keys, z.string());
// { id?: string; name?: string; email?: string }

Mapas (Maps)

const StringNumberMap = z.map(z.string(), z.number());
type StringNumberMap = z.infer<typeof StringNumberMap>; // Map<string, number>
 
const myMap: StringNumberMap = new Map();
myMap.set("one", 1);
myMap.set("two", 2);
 
StringNumberMap.parse(myMap);

Conjuntos (Sets)

const NumberSet = z.set(z.number());
type NumberSet = z.infer<typeof NumberSet>; // Set<number>
 
const mySet: NumberSet = new Set();
mySet.add(1);
mySet.add(2);
NumberSet.parse(mySet);

Los esquemas de conjuntos se pueden restringir aún más con los siguientes métodos de utilidad.

z.set(z.string()).min(5); // must contain 5 or more items
z.set(z.string()).max(5); // must contain 5 or fewer items
z.set(z.string()).size(5); // must contain 5 items exactly

Archivos (Files)

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
fileSchema.mime(["image/png", "image/jpeg"]); // multiple MIME types

Promesas (Promises)

Obsoletoz.promise() está obsoleto en Zod 4. Hay muy pocos casos de uso válidos para un esquema Promise. Si sospechas que un valor podría ser una Promise, simplemente usa await antes de analizarlo con Zod.

Instancia de (Instanceof)

Puedes usar z.instanceof para verificar que la entrada es una instancia de una clase. Esto es útil para validar entradas contra clases que se exportan desde bibliotecas de terceros.

class Test {
  name: string;
}
 
const TestSchema = z.instanceof(Test);
 
TestSchema.parse(new Test()); // ✅
TestSchema.parse("whatever"); // ❌

Propiedad (Property)

Para validar una propiedad particular de una instancia de clase contra un esquema Zod:

const blobSchema = z.instanceof(URL).check(
  z.property("protocol", z.literal("https:" as string, "Only HTTPS allowed"))
);
 
blobSchema.parse(new URL("https://example.com")); // ✅
blobSchema.parse(new URL("http://example.com")); // ❌

The z.property() API works with any data type (but it's most useful when used in conjunction with z.instanceof()).

const blobSchema = z.string().check(
  z.property("length", z.number().min(10))
);
 
blobSchema.parse("hello there!"); // ✅
blobSchema.parse("hello."); // ❌

Refinamientos (Refinements)

Cada esquema Zod almacena un array de refinamientos. Los refinamientos son una forma de realizar una validación personalizada para la que Zod no proporciona una API nativa.

.refine()

const myString = z.string().refine((val) => val.length <= 255);

Las funciones de refinamiento nunca deberían lanzar errores. En su lugar, deben devolver un valor falso (falsy) para indicar un error. Los errores lanzados no son capturados por Zod.

error

Para personalizar el mensaje de error:

const myString = z.string().refine((val) => val.length > 8, { 
  error: "Too short!" 
});

abort

Por defecto, los problemas de validación de las comprobaciones se consideran continuables; es decir, Zod ejecutará todas las comprobaciones en secuencia, incluso si una de ellas causa un error de validación. Esto suele ser deseable, ya que significa que Zod puede mostrar tantos errores como sea posible de una sola vez.

const myString = z.string()
  .refine((val) => val.length > 8, { error: "Too short!" })
  .refine((val) => val === val.toLowerCase(), { error: "Must be lowercase" });
  
 
const result = myString.safeParse("OH NO");
result.error?.issues;
/* [
  { "code": "custom", "message": "Too short!" },
  { "code": "custom", "message": "Must be lowercase" }
] */

Para marcar un refinamiento particular como no continuable, usa el parámetro abort. La validación terminará si la comprobación falla.

const myString = z.string()
  .refine((val) => val.length > 8, { error: "Too short!", abort: true })
  .refine((val) => val === val.toLowerCase(), { error: "Must be lowercase", abort: true });
 
 
const result = myString.safeParse("OH NO");
result.error?.issues;
// => [{ "code": "custom", "message": "Too short!" }]

path

Para personalizar la ruta del error, usa el parámetro path. Esto suele ser útil solo en el contexto de esquemas de objetos.

const passwordForm = z
  .object({
    password: z.string(),
    confirm: z.string(),
  })
  .refine((data) => data.password === data.confirm, {
    message: "Passwords don't match",
    path: ["confirm"], // path of error
  });

Esto establecerá el parámetro path en el problema asociado:

const result = passwordForm.safeParse({ password: "asdf", confirm: "qwer" });
result.error.issues;
/* [{
  "code": "custom",
  "path": [ "confirm" ],
  "message": "Passwords don't match"
}] */

Para definir un refinamiento asíncrono, simplemente pasa una función async:

const userId = z.string().refine(async (id) => {
  // verify that ID exists in database
  return true;
});

Si usas refinamientos asíncronos, ¡debes usar el método .parseAsync para analizar los datos! De lo contrario, Zod lanzará un error.

const result = await userId.parseAsync("abc123");

when

Nota — Esta es una característica para usuarios avanzados y puede ser abusada de maneras que aumentarán la probabilidad de errores no detectados que se originan dentro de tus refinamientos.

Por defecto, los refinamientos no se ejecutan si ya se han encontrado problemas no continuables. Zod tiene cuidado de asegurar que la firma de tipo del valor sea correcta antes de pasarlo a cualquier función de refinamiento.

const schema = z.string().refine((val) => {
  return val.length > 8
});
 
schema.parse(1234); // invalid_type: refinement won't be executed

En algunos casos, deseas un control más preciso sobre cuándo se ejecutan los refinamientos. Por ejemplo, considera esta verificación de "confirmación de contraseña":

const schema = z
  .object({
    password: z.string().min(8),
    confirmPassword: z.string(),
    anotherField: z.string(),
  })
  .refine((data) => data.password === data.confirmPassword, {
    message: "Passwords do not match",
    path: ["confirmPassword"],
  });
 
schema.parse({
  password: "asdf",
  confirmPassword: "asdf",
  anotherField: 1234 // ❌ this error will prevent the password check from running
});

Un error en anotherField evitará que se ejecute la verificación de confirmación de contraseña, aunque la verificación no dependa de anotherField. Para controlar cuándo se ejecutará un refinamiento, usa el parámetro when:

const schema = z
  .object({
    password: z.string().min(8),
    confirmPassword: z.string(),
    anotherField: z.string(),
  })
  .refine((data) => data.password === data.confirmPassword, {
    message: "Passwords do not match",
    path: ["confirmPassword"],
 
    // run if password & confirmPassword are valid
    when(payload) { 
      return schema 
        .pick({ password: true, confirmPassword: true }) 
        .safeParse(payload.value).success; 
    },  
  });
 
schema.parse({
  password: "asdf",
  confirmPassword: "asdf",
  anotherField: 1234 // ❌ this error will not prevent the password check from running
});

.superRefine()

La API normal .refine solo genera un único problema con un código de error "custom", pero .superRefine() hace posible crear múltiples problemas utilizando cualquiera de los tipos de problemas internos de Zod.

const UniqueStringArray = z.array(z.string()).superRefine((val, ctx) => {
  if (val.length > 3) {
    ctx.addIssue({
      code: "too_big",
      maximum: 3,
      origin: "array",
      inclusive: true,
      message: "Too many items 😡",
      input: val,
    });
  }
 
  if (val.length !== new Set(val).size) {
    ctx.addIssue({
      code: "custom",
      message: `No duplicates allowed.`,
      input: val,
    });
  }
});
 

.check()

Nota — La API .check() es una API de más bajo nivel que generalmente es más compleja que .superRefine(). Puede ser más rápida en rutas de código sensibles al rendimiento, pero también es más verbosa.

Códecs (Codecs)

New — Introducido en Zod 4.1. Consulta la página dedicada a Códecs para obtener más información.

Los códecs son un tipo especial de esquema que implementan transformaciones bidireccionales entre otros dos esquemas.

const stringToDate = z.codec(
  z.iso.datetime(),  // input schema: ISO date string
  z.date(),          // output schema: Date object
  {
    decode: (isoString) => new Date(isoString), // ISO string → Date
    encode: (date) => date.toISOString(),       // Date → ISO string
  }
);

Una operación regular .parse() realiza la transformación hacia adelante. Llama a la función decode del códec.

stringToDate.parse("2024-01-15T10:30:00.000Z"); // => Date

Alternativamente, puedes usar la función de nivel superior z.decode(). A diferencia de .parse() (que acepta una entrada unknown), z.decode() espera una entrada fuertemente tipada (string en este ejemplo).

z.decode(stringToDate, "2024-01-15T10:30:00.000Z"); // => Date

Para realizar la transformación inversa, usa la inversa: z.encode().

z.encode(stringToDate, new Date("2024-01-15")); // => "2024-01-15T00:00:00.000Z"

Consulta la página dedicada a Códecs para obtener más información. Esa página contiene implementaciones para códecs comúnmente necesarios que puedes copiar/pegar en tu proyecto:

Tuberías (Pipes)

Los esquemas se pueden encadenar en "tuberías" (pipes). Las tuberías son principalmente útiles cuando se usan junto con Transformaciones.

const stringToLength = z.string().pipe(z.transform(val => val.length));
 
stringToLength.parse("hello"); // => 5

Transformaciones (Transforms)

Nota — Para transformaciones bidireccionales, usa códecs.

Las transformaciones son un tipo especial de esquema que realizan una transformación unidireccional. En lugar de validar la entrada, aceptan cualquier cosa y realizan alguna transformación en los datos. Para definir una transformación:

const castToString = z.transform((val) => String(val));
 
castToString.parse("asdf"); // => "asdf"
castToString.parse(123); // => "123"
castToString.parse(true); // => "true"

Las funciones de transformación nunca deben lanzar errores. Los errores lanzados no son capturados por Zod.

Para realizar la lógica de validación dentro de una transformación, usa ctx. Para reportar un problema de validación, agrega un nuevo problema a ctx.issues (similar a la API .check()).

const coercedInt = z.transform((val, ctx) => {
  try {
    const parsed = Number.parseInt(String(val));
    return parsed;
  } catch (e) {
    ctx.issues.push({
      code: "custom",
      message: "Not a number",
      input: val,
    });
 
    // this is a special constant with type `never`
    // returning it lets you exit the transform without impacting the inferred return type
    return z.NEVER;
  }
});

Más comúnmente, las transformaciones se usan junto con Tuberías. Esta combinación es útil para realizar alguna validación inicial, luego transformar los datos analizados a otra forma.

const stringToLength = z.string().pipe(z.transform(val => val.length));
 
stringToLength.parse("hello"); // => 5

.transform()

Conectar (piping) algún esquema en una transformación es un patrón común, por lo que Zod proporciona un método de conveniencia .transform().

const stringToLength = z.string().transform(val => val.length); 

Las transformaciones también pueden ser asíncronas:

const idToUser = z
  .string()
  .transform(async (id) => {
    // fetch user from database
    return db.getUserById(id); 
  });
 
const user = await idToUser.parseAsync("abc123");

Si usas transformaciones asíncronas, ¡debes usar .parseAsync o .safeParseAsync al analizar datos! De lo contrario, Zod lanzará un error.

.preprocess()

Conectar (piping) una transformación en otro esquema es otro patrón común, por lo que Zod proporciona una función de conveniencia z.preprocess().

const coercedInt = z.preprocess((val) => {
  if (typeof val === "string") {
    return Number.parseInt(val);
  }
  return val;
}, z.int());

Valores por defecto (Defaults)

Para establecer un valor por defecto para un esquema:

const defaultTuna = z.string().default("tuna");
 
defaultTuna.parse(undefined); // => "tuna"

Alternativamente, puedes pasar una función que se volverá a ejecutar cada vez que se necesite generar un valor por defecto:

const randomDefault = z.number().default(Math.random);
 
randomDefault.parse(undefined);    // => 0.4413456736055323
randomDefault.parse(undefined);    // => 0.1871840107401901
randomDefault.parse(undefined);    // => 0.7223408162401552

Prefaults

En Zod, establecer un valor por defecto cortocircuitará el proceso de análisis. Si la entrada es undefined, el valor por defecto se devuelve ansiosamente. Como tal, el valor por defecto debe ser asignable al tipo de salida del esquema.

const schema = z.string().transform(val => val.length).default(0);
schema.parse(undefined); // => 0

A veces, es útil definir un valor prefault ("valor por defecto pre-análisis"). Si la entrada es undefined, el valor prefault se analizará en su lugar. El proceso de análisis no se cortocircuita. Como tal, el valor prefault debe ser asignable al tipo de entrada del esquema.

z.string().transform(val => val.length).prefault("tuna");
schema.parse(undefined); // => 4

Esto también es útil si deseas pasar algún valor de entrada a través de algunos refinamientos mutantes.

const a = z.string().trim().toUpperCase().prefault("  tuna  ");
a.parse(undefined); // => "TUNA"
 
const b = z.string().trim().toUpperCase().default("  tuna  ");
b.parse(undefined); // => "  tuna  "

Catch

Usa .catch() para definir un valor de respaldo que se devolverá en caso de un error de validación:

const numberWithCatch = z.number().catch(42);
 
numberWithCatch.parse(5); // => 5
numberWithCatch.parse("tuna"); // => 42

Alternativamente, puedes pasar una función que se volverá a ejecutar cada vez que se necesite generar un valor de captura (catch).

const numberWithRandomCatch = z.number().catch((ctx) => {
  ctx.error; // the caught ZodError
  return Math.random();
});
 
numberWithRandomCatch.parse("sup"); // => 0.4413456736055323
numberWithRandomCatch.parse("sup"); // => 0.1871840107401901
numberWithRandomCatch.parse("sup"); // => 0.7223408162401552

Tipos de marca (Branded types)

El sistema de tipos de TypeScript es estructural, lo que significa que dos tipos que son estructuralmente equivalentes se consideran el mismo.

type Cat = { name: string };
type Dog = { name: string };
 
const pluto: Dog = { name: "pluto" };
const simba: Cat = pluto; // works fine

En algunos casos, puede ser deseable simular el tipado nominal dentro de TypeScript. Esto se puede lograr con tipos de marca (branded types) (también conocidos como "tipos opacos").

const Cat = z.object({ name: z.string() }).brand<"Cat">();
const Dog = z.object({ name: z.string() }).brand<"Dog">();
 
type Cat = z.infer<typeof Cat>; // { name: string } & z.$brand<"Cat">
type Dog = z.infer<typeof Dog>; // { name: string } & z.$brand<"Dog">
 
const pluto = Dog.parse({ name: "pluto" });
const simba: Cat = pluto; // ❌ not allowed

Internamente, esto funciona adjuntando una "marca" (brand) al tipo inferido del esquema.

const Cat = z.object({ name: z.string() }).brand<"Cat">();
type Cat = z.infer<typeof Cat>; // { name: string } & z.$brand<"Cat">

Con esta marca, cualquier estructura de datos plana (sin marca) ya no es asignable al tipo inferido. Tienes que analizar algunos datos con el esquema para obtener datos con marca.

Ten en cuenta que los tipos de marca no afectan el resultado en tiempo de ejecución de .parse. Es una construcción solo estática.

Readonly

Para marcar un esquema como de solo lectura (readonly):

const ReadonlyUser = z.object({ name: z.string() }).readonly();
type ReadonlyUser = z.infer<typeof ReadonlyUser>;
// Readonly<{ name: string }>

El tipo inferido de los nuevos esquemas se marcará como readonly. Ten en cuenta que en TypeScript, esto solo afecta a objetos, arrays, tuplas, Set y Map:

z.object({ name: z.string() }).readonly(); // { readonly name: string }
z.array(z.string()).readonly(); // readonly string[]
z.tuple([z.string(), z.number()]).readonly(); // readonly [string, number]
z.map(z.string(), z.date()).readonly(); // ReadonlyMap<string, Date>
z.set(z.string()).readonly(); // ReadonlySet<string>

Las entradas se analizarán normalmente, luego el resultado se congelará con Object.freeze() para evitar modificaciones.

const result = ReadonlyUser.parse({ name: "fido" });
result.name = "simba"; // throws TypeError

JSON

Para validar cualquier valor codificable en JSON:

const jsonSchema = z.json();

Esta es una API de conveniencia que devuelve el siguiente esquema de unión:

const jsonSchema = z.lazy(() => {
  return z.union([
    z.string(params), 
    z.number(), 
    z.boolean(), 
    z.null(), 
    z.array(jsonSchema), 
    z.record(z.string(), jsonSchema)
  ]);
});

Funciones (Functions)

Zod proporciona una utilidad z.function() para definir funciones validadas por Zod. De esta manera, puedes evitar mezclar el código de validación con tu lógica de negocio.

const MyFunction = z.function({
  input: [z.string()], // parameters (must be an array or a ZodTuple)
  output: z.number()  // return type
});
 
type MyFunction = z.infer<typeof MyFunction>;
// (input: string) => number

Los esquemas de función tienen un método .implement() que acepta una función y devuelve una nueva función que valida automáticamente sus entradas y salidas.

const computeTrimmedLength = MyFunction.implement((input) => {
  // TypeScript knows input is a string!
  return input.trim().length;
});
 
computeTrimmedLength("sandwich"); // => 8
computeTrimmedLength(" asdf "); // => 4

Esta función lanzará un ZodError si la entrada no es válida:

computeTrimmedLength(42); // throws ZodError

Si solo te importa validar las entradas, puedes omitir el campo output.

const MyFunction = z.function({
  input: [z.string()], // parameters (must be an array or a ZodTuple)
});
 
const computeTrimmedLength = MyFunction.implement((input) => input.trim.length);

Usa el método .implementAsync() para crear una función asíncrona.

const computeTrimmedLengthAsync = MyFunction.implementAsync(
  async (input) => input.trim().length
);
 
computeTrimmedLengthAsync("sandwich"); // => Promise<8>

Personalizado (Custom)

Puedes crear un esquema Zod para cualquier tipo de TypeScript usando z.custom(). Esto es útil para crear esquemas para tipos que no son soportados por Zod de forma predeterminada, como los literales de cadena de plantilla.

const px = z.custom<`${number}px`>((val) => {
  return typeof val === "string" ? /^\d+px$/.test(val) : false;
});
 
type px = z.infer<typeof px>; // `${number}px`
 
px.parse("42px"); // "42px"
px.parse("42vw"); // throws;

Si no proporcionas una función de validación, Zod permitirá cualquier valor. ¡Esto puede ser peligroso!

z.custom<{ arg: string }>(); // performs no validation

Puedes personalizar el mensaje de error y otras opciones pasando un segundo argumento. Este parámetro funciona de la misma manera que el parámetro params de .refine.

z.custom<...>((val) => ..., "custom error message");