Zod Infer
Derive TypeScript types from Zod schemas with z.infer, z.input, and z.output - single source of truth for both runtime validation and compile-time safety.
Busque em todas as páginas da documentação
Derive TypeScript types from Zod schemas with z.infer, z.input, and z.output - single source of truth for both runtime validation and compile-time safety.
🤖 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";
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1),
email: z.string().email(),
role: z.enum(["admin", "editor", "viewer"]),
createdAt: z.string().datetime().transform((s) => new Date(s)),
});
// z.infer = z.output - o tipo DEPOIS das transformações
type User = z.infer<typeof UserSchema>;
// { id: string; name: string; email: string; role: "admin" | "editor" | "viewer"; createdAt: Date }
// z.input - o tipo ANTES das transformações (o que você passa)
type UserInput = z.input<typeof UserSchema>;
// { id: string; name: string; email: string; role: "admin" | "editor" | "viewer"; createdAt: string }
// z.output - o mesmo que z.infer
type UserOutput = z.output<typeof UserSchema>;Quando usar isso: Sempre que você tiver um schema Zod e precisar de um tipo TypeScript correspondente - para props, estado, payloads de API, linhas de banco de dados ou assinaturas de função.
"use client";
import { useState } from "react";
import { z } from "zod";
// O schema é a única fonte de verdade
const TodoSchema = z.object({
id: z.string().uuid(),
title: z.string().min(1, "Título obrigatório").max(200),
completed: z.boolean().default(false),
priority: z.coerce.number().int().min(1).max(5).default(3),
tags: z.array(z.string()).default([]),
});
// Derive todos os tipos do schema
type Todo = z.infer<typeof TodoSchema>;
type TodoInput = z.input<typeof TodoSchema>;
// Schema de criação - omita campos gerados automaticamente
const CreateTodoSchema = TodoSchema.omit({ id: true });
type CreateTodoInput = z.input<typeof CreateTodoSchema>;
// Schema de atualização - tudo parcial, exceto id
const UpdateTodoSchema = TodoSchema.partial().required({ id: true });
type UpdateTodo = z.infer<typeof UpdateTodoSchema>;
export function TodoManager() {
const [todos, setTodos] = useState<Todo[]>([]);
const [error, setError] = useState("");
function addTodo(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const fd = new FormData(e.currentTarget);
const raw: CreateTodoInput = {
title: fd.get("title") as string,
completed: false,
priority: fd.get("priority") as unknown as number,
tags: (fd.get("tags") as string).split(",").filter(Boolean).map((t) => t.trim()),
};
const result = CreateTodoSchema.safeParse(raw);
if (!result.success) {
setError(result.error.issues.map((i) => i.message).join(", "));
return;
}
const newTodo: Todo = { ...result.data, id: crypto.randomUUID() };
setTodos((prev) => [...prev, newTodo]);
setError("");
e.currentTarget.reset();
}
return (
<div className="max-w-md space-y-4">
<form onSubmit={addTodo} className="flex flex-col gap-2">
<input name="title" placeholder="Título da Tarefa" className="rounded border p-2" />
<input name="priority" type="number" min={1} max={5} defaultValue={3} className="rounded border p-2" />
<input name="tags" placeholder="Tags (separadas por vírgula)" className="rounded border p-2" />
<button type="submit" className="rounded bg-blue-600 px-4 py-2 text-white">Adicionar</button>
{error && <p className="text-sm text-red-600">{error}</p>}
</form>
<ul className="space-y-1">
{todos.map((t) => (
<li key={t.id} className="rounded border p-2 text-sm">
<strong>{t.title}</strong> - Prioridade: {t.priority} - Tags: {t.tags.join(", ") || "nenhuma"}
</li>
))}
</ul>
</div>
);
}O que isso demonstra:
z.infer para o tipo de saída validadoz.input para o tipo de entrada bruto (antes de padrões e transformações).omit(), .partial(), .required()z.infer<typeof Schema> extrai o tipo TypeScript que parse() retorna (o tipo de saída)z.input<typeof Schema> extrai o tipo que parse() aceita (antes de transformações e padrões)z.input e z.output são idênticos.optional(), .nullable() podem fazer com que entrada e saída difiram.pick(), .omit(), .extend(), .partial()) produzem novos schemas com tipos inferidos corretamenteDerivando tipos CRUD de um único schema:
const ItemSchema = z.object({
id: z.string().uuid(),
name: z.string(),
price: z.number().positive(),
updatedAt: z.date(),
});
type Item = z.infer<typeof ItemSchema>;
type CreateItem = z.infer<typeof ItemSchema.omit({ id: true, updatedAt: true })>;
type UpdateItem = z.infer<typeof ItemSchema.partial().required({ id: true })>;
type ItemSummary = z.infer<typeof ItemSchema.pick({ id: true, name: true })>;Usando tipos inferidos em assinaturas de função:
const ApiResponse = z.object({
data: z.array(UserSchema),
total: z.number(),
page: z.number(),
});
type ApiResponse = z.infer<typeof ApiResponse>;
async function fetchUsers(page: number): Promise<ApiResponse> {
const res = await fetch(`/api/users?page=${page}`);
return ApiResponse.parse(await res.json());
}Componentes genéricos baseados em schema:
function SchemaForm<T extends z.ZodObject<any>>({
schema,
onSubmit,
}: {
schema: T;
onSubmit: (data: z.infer<T>) => void;
}) {
// Constrói campos de formulário a partir de schema.shape
const fields = Object.keys(schema.shape);
// ...
}// z.infer é um alias para z.output
type Infer<T extends z.ZodType> = T["_output"];
type Input<T extends z.ZodType> = T["_input"];
type Output<T extends z.ZodType> = T["_output"];
// Padrões tornam os campos opcionais na entrada, mas obrigatórios na saída
const S = z.object({ count: z.number().default(0) });
type SIn = z.input<typeof S>; // { count?: number | undefined }
type SOut = z.output<typeof S>; // { count: number }
// Use satisfies com tipos inferidos para constantes type-safe
const defaultUser = {
name: "Convidado",
email: "convidado@exemplo.com",
role: "viewer" as const,
} satisfies Partial<User>;z.infer é saída, não entrada - Se o seu schema tiver transformações, z.infer fornecerá o tipo pós-transformação. O estado do seu formulário e o corpo da requisição da API precisam de z.input. Correção: Use z.input para formas de dados de formulário e requisições, z.infer (ou z.output) para resultados validados.
Padrões são invisíveis para z.infer - z.string().default("oi") infere como string em z.infer, não string | undefined. Mas z.input o mostra corretamente como string | undefined. Correção: Esteja ciente da assimetria ao construir objetos de entrada.
Não é possível usar z.infer sem typeof - z.infer<UserSchema> está incorreto. Correção: Sempre escreva z.infer<typeof UserSchema>.
Referências circulares precisam de anotação explícita - Schemas recursivos com z.lazy não podem ser inferidos automaticamente. Correção: Declare o tipo manualmente e anote o schema: const S: z.ZodType<MyType> = z.lazy(...).
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Interfaces TypeScript manuais | Você não tem necessidades de validação em tempo de execução | Você quer uma única fonte de verdade para tipos e validação |
| TypeBox | Você precisa de saída JSON Schema junto com tipos TypeScript | Você não precisa de interoperabilidade com JSON Schema |
Valibot InferOutput | Você usa Valibot e quer o mesmo padrão | Você está padronizado em Zod |
| Inferência do tRPC a partir de routers | Seus tipos fluem através do tRPC de ponta a ponta | Você precisa de validação autônoma fora do tRPC |
z.infer é um alias para z.output -- o tipo após transformações e padrõesz.input é o tipo antes de transformações e padrões -- o que você passa para parse()Use z.input para estado de formulário, corpos de requisição e qualquer código que construa os dados brutos antes da análise. Use z.infer (saída) para o resultado validado após a análise.
const Item = z.object({ id: z.string(), name: z.string(), price: z.number() });
type CreateItem = z.infer<typeof Item.omit({ id: true })>;
type UpdateItem = z.infer<typeof Item.partial().required({ id: true })>;
type ItemSummary = z.infer<typeof Item.pick({ id: true, name: true })>;const S = z.object({ count: z.number().default(0) });
type SIn = z.input<typeof S>; // { count?: number | undefined }
type SOut = z.output<typeof S>; // { count: number }O campo é opcional na entrada, mas garantido na saída.
const S = z.string().transform((s) => new Date(s));
type SIn = z.input<typeof S>; // string
type SOut = z.output<typeof S>; // Dateconst ApiResponse = z.object({ data: z.array(UserSchema), total: z.number() });
type ApiResponse = z.infer<typeof ApiResponse>;
async function fetchUsers(): Promise<ApiResponse> {
const res = await fetch("/api/users");
return ApiResponse.parse(await res.json());
}.pick(), .omit(), .extend(), .merge(), .partial(), .required(), e .deepPartial() retornam todos novos schemas cujos tipos z.infer são atualizados automaticamente.
Você deve usar typeof: z.infer<typeof UserSchema>. O parâmetro genérico espera o tipo da variável do schema, não o valor em si.
TypeScript não consegue inferir tipos recursivos de z.lazy. Você deve declarar o tipo manualmente e anotar o schema:
type Category = { name: string; children: Category[] };
const CategorySchema: z.ZodType<Category> = z.lazy(() =>
z.object({ name: z.string(), children: z.array(CategorySchema) })
);function SchemaForm<T extends z.ZodObject<any>>({
schema,
onSubmit,
}: {
schema: T;
onSubmit: (data: z.infer<T>) => void;
}) {
const fields = Object.keys(schema.shape);
// ...
}z.infer<T> resolve para T["_output"]z.input<T> resolve para T["_input"]ZodTypeconst defaultUser = {
name: "Convidado",
email: "convidado@exemplo.com",
role: "viewer" as const,
} satisfies Partial<User>;Isso verifica se o objeto corresponde ao tipo sem alargá-lo.
Revisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥