Noções Básicas de Zod
Defina esquemas, valide dados em tempo de execução e trate erros - a base da validação type-safe em TypeScript.
Busque em todas as páginas da documentação
Defina esquemas, valide dados em tempo de execução e trate erros - a base da validação type-safe em TypeScript.
🤖 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";
// 1. Defina um esquema
const UserSchema = z.object({
name: z.string().min(1, "O nome é obrigatório"),
email: z.string().email("Email inválido"),
age: z.number().int().min(18, "Deve ter 18+ anos"),
});
// 2. Parse (lança em caso de falha)
try {
const user = UserSchema.parse({ name: "Ada", email: "ada@example.com", age: 30 });
console.log(user); // tipado como { name: string; email: string; age: number }
} catch (err) {
if (err instanceof z.ZodError) {
console.error(err.issues);
}
}
// 3. safeParse (nunca lança)
const result = UserSchema.safeParse({ name: "", email: "bad", age: 15 });
if (!result.success) {
console.error(result.error.flatten());
} else {
console.log(result.data);
}Quando usar isso: Sempre que precisar de validação em tempo de execução de entrada do usuário, respostas de API, variáveis de ambiente ou qualquer limite de dados não confiável.
"use client";
import { useState } from "react";
import { z } from "zod";
const ContactSchema = z.object({
name: z.string().min(1, "O nome é obrigatório").max(100, "Nome muito longo"),
email: z.string().email("Por favor, insira um email válido"),
message: z
.string()
.min(10, "A mensagem deve ter pelo menos 10 caracteres")
.max(1000, "Mensagem muito longa"),
});
type ContactData = z.infer<typeof ContactSchema>;
export function ContactValidator() {
const [errors, setErrors] = useState<Record<string, string[]>>({});
const [success, setSuccess] = useState<ContactData | null>(null);
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const raw = {
name: formData.get("name"),
email: formData.get("email"),
message: formData.get("message"),
};
const result = ContactSchema.safeParse(raw);
if (!result.success) {
const flat = result.error.flatten();
setErrors(flat.fieldErrors as Record<string, string[]>);
setSuccess(null);
} else {
setErrors({});
setSuccess(result.data);
}
}
return (
<form onSubmit={handleSubmit} className="flex max-w-md flex-col gap-3">
<div>
<input name="name" placeholder="Nome" className="w-full rounded border p-2" />
{errors.name && <p className="text-sm text-red-600">{errors.name[0]}</p>}
</div>
<div>
<input name="email" placeholder="Email" className="w-full rounded border p-2" />
{errors.email && <p className="text-sm text-red-600">{errors.email[0]}</p>}
</div>
<div>
<textarea name="message" placeholder="Mensagem" className="w-full rounded border p-2" />
{errors.message && <p className="text-sm text-red-600">{errors.message[0]}</p>}
</div>
<button type="submit" className="rounded bg-blue-600 px-4 py-2 text-white">
Validar
</button>
{success && (
<pre className="rounded bg-green-50 p-3 text-sm">{JSON.stringify(success, null, 2)}</pre>
)}
</form>
);
}O que isso demonstra:
safeParse para validação sem lançamento de erroflatten() para obter mensagens de erro por campoz.infer para derivar tipos TypeScript de esquemasparse() retorna os dados validados ou lança um ZodError contendo um array issuessafeParse() retorna uma união discriminada: { success: true, data } ou { success: false, error }ZodError.flatten() agrupa erros em formErrors (raiz) e fieldErrors (arrays por campo)ZodError.format() retorna um objeto aninhado que corresponde à forma do esquema, útil para objetos profundos.passthrough() ou .strict() para alterar)Mapas de erro personalizados:
const schema = z.string({
required_error: "Este campo é obrigatório",
invalid_type_error: "Esperava uma string",
}).min(1, { message: "Não pode estar vazio" });Mapa de erro global:
z.setErrorMap((issue, ctx) => {
if (issue.code === z.ZodIssueCode.too_small) {
return { message: `O comprimento mínimo é ${issue.minimum}` };
}
return { message: ctx.defaultError };
});Erros achatados vs. formatados:
const result = schema.safeParse(data);
if (!result.success) {
// Achatado - ótimo para formulários simples
result.error.flatten();
// { formErrors: string[], fieldErrors: { name?: string[], email?: string[] } }
// Formatado - ótimo para objetos aninhados
result.error.format();
// { name: { _errors: string[] }, address: { city: { _errors: string[] } } }
}// O tipo inferido corresponde exatamente ao esquema
type User = z.infer<typeof UserSchema>;
// { name: string; email: string; age: number }
// ZodError é genérico - você pode tipá-lo
const result = UserSchema.safeParse(data);
if (!result.success) {
const err: z.ZodError<User> = result.error;
}
// Use z.ZodType para aceitar qualquer esquema como parâmetro
function validate<T>(schema: z.ZodType<T>, data: unknown): T {
return schema.parse(data);
}parse vs safeParse em server actions - Usar parse dentro de uma server action lançará um erro não tratado. Correção: Sempre use safeParse em server actions e retorne erros estruturados para o cliente.
Coerção de string do FormData - FormData.get() retorna string | File | null, mas seu esquema espera number. Correção: Use z.coerce.number() ou z.string().pipe(z.coerce.number()) ao analisar dados de formulário.
Remoção silenciosa de chaves desconhecidas - z.object() descarta chaves extras por padrão. Se você precisar delas, use .passthrough(). Se quiser rejeitá-las, use .strict().
Localização das mensagens de erro - As mensagens de erro do Zod são em inglês por padrão. Correção: Use um errorMap personalizado para i18n.
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Yup | Você quer uma biblioteca de esquema nativa do Formik com uma API semelhante | Você precisa de inferência TypeScript de primeira linha |
| Valibot | Você precisa do menor tamanho de pacote possível | Você depende das extensas integrações de ecossistema do Zod |
| ArkType | Você quer validação em nível de tipo com sobrecarga de tempo de execução quase zero | Você precisa de amplo suporte da comunidade e exemplos |
| Validação manual | Verificações únicas com lógica trivial | Você tem mais de 2-3 campos ou objetos aninhados |
De uma aplicação SaaS de produção Next.js 15 / React 19 (SystemsArchitect.io).
// Exemplo de produção: Esquema de Banner com padrões, anulável e validação entre campos
// Arquivo: src/schemas/banner.ts
import { z } from 'zod';
export const BannerSchema = z.object({
title: z.string().min(1, 'O título é obrigatório').max(100),
subtitle: z.string().optional().default(''),
imageUrl: z.string().url('Deve ser uma URL válida').nullable(),
linkUrl: z.string().url('Deve ser uma URL válida').optional(),
linkText: z.string().optional().default('Saiba mais'),
isActive: z.boolean().default(true),
startDate: z.coerce.date(),
endDate: z.coerce.date().nullable(),
priority: z.number().int().min(0).max(100).default(50),
}).refine(
(data) => {
if (data.endDate && data.startDate) {
return data.endDate > data.startDate;
}
return true;
},
{
message: 'A data de término deve ser posterior à data de início',
path: ['endDate'],
}
);
// Extrai o tipo TypeScript do esquema
export type Banner = z.infer<typeof BannerSchema>;
// {
// title: string;
// subtitle: string; // padrão ''
// imageUrl: string | null; // anulável
// linkUrl?: string; // opcional, sem padrão
// linkText: string; // padrão 'Saiba mais'
// isActive: boolean; // padrão true
// startDate: Date;
// endDate: Date | null;
// priority: number; // padrão 50
// }O que isso demonstra em produção:
.optional() significa que o campo pode ser undefined (omitido da entrada). .nullable() significa que pode ser explicitamente null. Estes são diferentes: imageUrl deve estar presente, mas pode ser null, enquanto linkUrl pode ser omitido completamente..optional().default('') torna um campo opcional na entrada, mas garante um valor na saída. Após a análise, subtitle é sempre uma string, nunca undefined..refine() habilita a validação entre campos que z.string() ou z.date() sozinhos não conseguem expressar. A opção path: ['endDate'] anexa a mensagem de erro ao campo correto nas exibições de erro do formulário.z.coerce.date() converte automaticamente entradas de string (como "2025-01-15" de um input de data HTML ou payload JSON) em objetos Date. Sem coerção, passar uma string para um esquema z.date() falharia.z.infer<typeof BannerSchema> extrai o tipo TypeScript do esquema. Esta é a única fonte de verdade. Você nunca escreve manualmente uma interface correspondente, o que elimina a divergência entre validação e tipos.parse() retorna os dados validados ou lança um ZodErrorsafeParse() nunca lança; ele retorna { success: true, data } ou { success: false, error }safeParse em server actions e em qualquer lugar onde você precise lidar com erros graciosamenteconst result = schema.safeParse(data);
if (!result.success) {
const flat = result.error.flatten();
// flat.fieldErrors = { name?: string[], email?: string[] }
}flatten() retorna { formErrors: string[], fieldErrors: { [key]: string[] } } -- melhor para formulários simplesformat() retorna um objeto aninhado que corresponde à forma do esquema com arrays _errors -- melhor para objetos profundamente aninhadosconst UserSchema = z.object({
name: z.string(),
age: z.number(),
});
type User = z.infer<typeof UserSchema>;
// { name: string; age: number }Ele as remove silenciosamente. Use .passthrough() para manter chaves extras ou .strict() para rejeitá-las com um erro.
Use z.coerce.number() que chama Number(value) antes de validar, convertendo a string em um número automaticamente.
.optional() permite undefined (o tipo se torna T | undefined).nullable() permite null (o tipo se torna T | null).nullish() permite ambos (o tipo se torna T | null | undefined)z.setErrorMap((issue, ctx) => {
if (issue.code === z.ZodIssueCode.too_small) {
return { message: `O comprimento mínimo é ${issue.minimum}` };
}
return { message: ctx.defaultError };
});parse() lança um ZodError em caso de falha, que se torna um erro de servidor não tratado. Sempre use safeParse() em server actions e retorne erros estruturados para o cliente.
z.coerce.date() chama new Date(value), então aceita timestamps e números aleatórios. Se você quiser apenas strings de data ISO, use z.string().datetime() em vez disso.
Ele adiciona validação entre campos. Você fornece um predicado que recebe o objeto completo analisado e retorna true/false. Especifique path: ["fieldName"] para anexar o erro a um campo específico.
function validate<T>(schema: z.ZodType<T>, data: unknown): T {
return schema.parse(data);
}Sim. z.ZodError<User> fornece um erro tipado cujos issues referenciam os campos de User. Isso é útil para utilitários de tratamento de erros type-safe.
Revisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥