Tipos Zod
Domine cada tipo primitivo e composto Zod - strings, números, arrays, objetos, enums e unions.
Busque em todas as páginas da documentação
Domine cada tipo primitivo e composto Zod - strings, números, arrays, objetos, enums e unions.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida - pronto para copiar e colar.
import { z } from "zod";
// Primitivos
const str = z.string().min(1).max(255);
const num = z.number().int().positive();
const bool = z.boolean();
const date = z.date();
const bigint = z.bigint();
// Strings com validadores embutidos
const email = z.string().email();
const url = z.string().url();
const uuid = z.string().uuid();
const cuid = z.string().cuid();
const regex = z.string().regex(/^[A-Z]{3}-\d{4}$/);
// Números
const price = z.number().min(0).max(999999).multipleOf(0.01);
const port = z.number().int().gte(1024).lte(65535);
// Arrays
const tags = z.array(z.string()).min(1).max(10);
const uniqueTags = z.array(z.string()).nonempty();
// Objetos
const User = z.object({
id: z.string().uuid(),
name: z.string(),
role: z.enum(["admin", "user", "guest"]),
});
// Enums
const Status = z.enum(["active", "inactive", "pending"]);
const NativeEnum = z.nativeEnum(MyEnum); // funciona com enums TS
// Unions e unions discriminadas
const Result = z.discriminatedUnion("status", [
z.object({ status: z.literal("ok"), data: z.string() }),
z.object({ status: z.literal("error"), message: z.string() }),
]);
// Opcionais, anuláveis, padrões
const optional = z.string().optional(); // string | undefined
const nullable = z.string().nullable(); // string | null
const nullish = z.string().nullish(); // string | null | undefined
const withDefault = z.string().default("N/A");Quando usar isso: Quando você precisar definir a forma e as restrições de qualquer estrutura de dados para validação.
"use client";
import { useState } from "react";
import { z } from "zod";
const ProductSchema = z.object({
name: z.string().min(1, "Nome do produto é obrigatório").max(100),
price: z.coerce.number().positive("O preço deve ser positivo"),
category: z.enum(["electronics", "clothing", "food", "other"]),
tags: z.array(z.string().min(1)).min(1, "Pelo menos uma tag").max(5),
metadata: z
.object({
weight: z.coerce.number().positive().optional(),
color: z.string().optional(),
})
.optional(),
});
type Product = z.infer<typeof ProductSchema>;
export function ProductForm() {
const [result, setResult] = useState<string>("");
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const fd = new FormData(e.currentTarget);
const raw = {
name: fd.get("name"),
price: fd.get("price"),
category: fd.get("category"),
tags: (fd.get("tags") as string)?.split(",").map((t) => t.trim()),
metadata: {
weight: fd.get("weight") || undefined,
color: fd.get("color") || undefined,
},
};
const parsed = ProductSchema.safeParse(raw);
if (parsed.success) {
setResult(JSON.stringify(parsed.data, null, 2));
} else {
setResult(parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("\n"));
}
}
return (
<form onSubmit={handleSubmit} className="flex max-w-md flex-col gap-3">
<input name="name" placeholder="Nome do produto" className="rounded border p-2" />
<input name="price" placeholder="Preço" type="number" step="0.01" className="rounded border p-2" />
<select name="category" className="rounded border p-2">
<option value="electronics">Eletrônicos</option>
<option value="clothing">Roupas</option>
<option value="food">Comida</option>
<option value="other">Outro</option>
</select>
<input name="tags" placeholder="Tags (separadas por vírgula)" className="rounded border p-2" />
<input name="weight" placeholder="Peso (opcional)" type="number" className="rounded border p-2" />
<input name="color" placeholder="Cor (opcional)" className="rounded border p-2" />
<button type="submit" className="rounded bg-blue-600 px-4 py-2 text-white">Validar</button>
{result && <pre className="rounded bg-gray-50 p-3 text-sm whitespace-pre-wrap">{result}</pre>}
</form>
);
}O que isso demonstra:
z.coerce.number() para conversão de string para número em formuláriosz.enum()ZodType e implementa internamente um método _parsez.string().min(1).email() cria um novo schema em cada etapaz.coerce.* chama o construtor nativo (Number(), String(), Boolean()) antes de validarz.enum() aceita uma tupla readonly de literais de string e infere um tipo unionz.discriminatedUnion() usa uma chave compartilhada para despachar para o ramo correto eficientementez.object() é rigoroso quanto à forma - use .extend(), .merge(), .pick(), .omit() para derivar novas formasEstendendo e mesclando objetos:
const Base = z.object({ id: z.string().uuid(), createdAt: z.date() });
const WithName = Base.extend({ name: z.string() });
const Merged = Base.merge(z.object({ email: z.string().email() }));
const Picked = Base.pick({ id: true });
const Omitted = Base.omit({ createdAt: true });
const Partial = Base.partial(); // todos os campos opcionais
const Required = Partial.required(); // todos os campos obrigatórios novamente
const DeepPartial = Base.deepPartial(); // objetos aninhados também parciaisTipos Record e Map:
const Config = z.record(z.string(), z.number());
// { [key: string]: number }
const Scores = z.map(z.string(), z.number());
// Map<string, number>Tuplas e tipos literais:
const Coord = z.tuple([z.number(), z.number()]);
const Tagged = z.tuple([z.literal("point"), z.number(), z.number()]);
const WithRest = z.tuple([z.string()]).rest(z.number());
// [string, ...number[]]Schemas preguiçosos (lazy) para tipos recursivos:
type Category = { name: string; children: Category[] };
const CategorySchema: z.ZodType<Category> = z.lazy(() =>
z.object({
name: z.string(),
children: z.array(CategorySchema),
})
);// z.enum produz um tipo union
const Role = z.enum(["admin", "user"]);
type Role = z.infer<typeof Role>; // "admin" | "user"
Role.enum.admin; // "admin" - acesso a constante tipada
Role.options; // ["admin", "user"] - a tupla
// nativeEnum funciona com enums TypeScript
enum Direction { Up = "UP", Down = "DOWN" }
const DirSchema = z.nativeEnum(Direction);
type Dir = z.infer<typeof DirSchema>; // Direction
// Discriminated unions inferem corretamente
type Result = z.infer<typeof Result>;
// { status: "ok"; data: string } | { status: "error"; message: string }z.enum requer as const - Se você passar uma variável de array simples, o TypeScript ampliará o tipo para string[]. Correção: Use z.enum(["a", "b"] as const) ou defina o array com as const primeiro.
z.coerce.date() aceita números - z.coerce.date() chama new Date(value), portanto, aceita timestamps e números arbitrários. Correção: Se você quiser apenas strings ISO, use z.string().datetime() em vez disso.
z.object não é z.record - z.object requer chaves exatas; z.record permite qualquer chave de um determinado tipo. Misturá-los leva a uma validação incorreta.
Union vs discriminatedUnion - z.union tenta cada ramo sequencialmente e retorna a primeira correspondência. z.discriminatedUnion usa um campo de chave para despacho O(1) e fornece melhores mensagens de erro. Correção: Prefira z.discriminatedUnion quando seus objetos compartilham um campo de tipo.
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Type guards TypeScript | Simples redução de tempo de execução sem biblioteca | Você precisa de schemas declarativos com mensagens de erro |
| io-ts | Você quer composição de codec no estilo programação funcional | Você prefere uma API mais simples e imperativa |
| Superstruct | Você precisa de validação baseada em struct com coerção personalizada | Você quer validadores .email(), .url() embutidos |
| JSON Schema (Ajv) | Você precisa de interoperabilidade de schema entre linguagens | Você quer inferência TypeScript rigorosa |
.email(), .url(), .uuid(), .cuid(), .regex(), .min(), .max(), .length(), .startsWith(), .endsWith(), .trim(), .datetime(), e .ip().
z.enum(["a", "b"]) aceita uma tupla de literais de string e infere um tipo unionz.nativeEnum(MyEnum) funciona com declarações enum do TypeScriptz.enum para código novo; use z.nativeEnum para enums TS existentesz.union tenta cada ramo sequencialmente e retorna a primeira correspondênciaz.discriminatedUnion usa um campo de chave compartilhado para despacho O(1) e melhores mensagens de erroz.discriminatedUnion quando objetos compartilham um campo de tipo/statusconst Base = z.object({ id: z.string(), name: z.string() });
const Extended = Base.extend({ email: z.string().email() });
const Picked = Base.pick({ id: true });
const Omitted = Base.omit({ id: true });
const Partial = Base.partial();
const DeepPartial = Base.deepPartial();const Config = z.record(z.string(), z.number());
// Tipo inferido: { [key: string]: number }Isso é diferente de z.object(), que requer chaves exatas.
const WithRest = z.tuple([z.string()]).rest(z.number());
// Tipo inferido: [string, ...number[]]type Category = { name: string; children: Category[] };
const CategorySchema: z.ZodType<Category> = z.lazy(() =>
z.object({
name: z.string(),
children: z.array(CategorySchema),
})
);Você deve declarar o tipo manualmente porque o TypeScript não consegue inferir tipos recursivos de z.lazy.
z.coerce.number() chama Number(value) na entrada antes de validar. Isso converte strings como "42" para 42. z.number() puro rejeita strings.
O TypeScript amplia o tipo do array para string[], perdendo os tipos literais. Corrija usando as const:
const roles = ["admin", "user"] as const;
const Role = z.enum(roles); // "admin" | "user"z.coerce.date() chama new Date(value), que aceita timestamps e números. new Date(999) é uma data válida. Se você quiser apenas strings ISO, use z.string().datetime() em vez disso.
.optional() permite undefined.nullable() permite null.default(value) torna o campo opcional na entrada, mas fornece um fallback na saídaconst Role = z.enum(["admin", "user"]);
Role.enum.admin; // "admin" (constante tipada)
Role.options; // ["admin", "user"] (a tupla)
type Role = z.infer<typeof Role>; // "admin" | "user"Ele produz um tipo union adequado onde cada ramo é restringido pela chave discriminante:
type Result = { status: "ok"; data: string } | { status: "error"; message: string };O TypeScript pode restringir o tipo verificando o campo status.
Revisado por Chris St. John·Última atualização: 19 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥