Contexto
Um campo que não recebe nenhuma regra explícita de obrigatoriedade se comporta de dois jeitos opostos dependendo da biblioteca de validação escolhida. E essa única decisão de design é a raiz de praticamente toda diferença prática entre Zod e Yup. Em Zod, todo campo de um z.object({...}) é obrigatório por padrão, e fica opcional só quando marcado explicitamente com .optional(). Em Yup, é o oposto: todo campo de um object({...}) é opcional por padrão, e só se torna obrigatório com .required(). Essa inversão se propaga direto para o tipo TypeScript inferido em cada schema, e é a fonte mais comum de superfície de bugs quando alguém troca de uma biblioteca para outra sem revisar os campos um a um.
Yup também é mais antigo no ecossistema JavaScript e nasceu antes da adoção em massa de TypeScript, o que se reflete em como o suporte a inferência de tipos foi incorporado — como uma camada sobre uma API já existente, em vez de ser o ponto de partida do design, como em Zod.
Quando escolher Zod
Zod encaixa quando o time quer que o schema seja a única fonte de verdade e o tipo TypeScript derive dele sem margem para divergência silenciosa, todo campo declarado é obrigatório, a menos que o contrário seja dito explicitamente:
import { z } from "zod";
const User = z.object({
email: z.email(),
name: z.string(),
});
type User = z.infer<typeof User>; // { email: string; name: string } — ambos obrigatórios
Nenhum campo do tipo User acima é opcional, porque nenhum foi marcado com .optional(): o comportamento padrão é o mais estrito. Esse ponto de partida reduz o risco de um campo se tornar acidentalmente opcional por esquecimento, o que é especialmente relevante em validação de payloads de API, onde um campo "esquecido como opcional" pode passar dados incompletos adiante sem erro. Zod também vem sendo o padrão adotado por bibliotecas mais recentes do ecossistema TypeScript — resolvers de formulário, geração de schema para tRPC e ferramentas de IA que precisam de schemas estruturados — o que amplia sua superfície de integração além de formulários tradicionais.
Quando escolher Yup
Yup continua sendo uma escolha sólida em projetos que já têm uma base de schemas Yup madura, ou que usam Formik — a combinação Formik + Yup é uma das mais antigas e documentadas do ecossistema React para validação de formulários:
import { object, string, InferType } from "yup";
const userSchema = object({
email: string().email(),
name: string().required(),
});
type User = InferType<typeof userSchema>;
// { email?: string | undefined; name: string } — email é opcional por padrão
Note que, no exemplo acima, email não tem .required() e por isso o tipo inferido o marca como opcional — mesmo estando presente no schema. Esse comportamento pode ser exatamente o que um formulário quer (campos que só se tornam obrigatórios sob certas condições, algo que Yup expressa bem com .when()), mas exige atenção redobrada de quem espera o comportamento "obrigatório por padrão" ao qual Zod acostumou parte do ecossistema. Yup também tem suporte maduro e antigo a validação condicional entre campos e a testes assíncronos, um caso de uso presente desde as primeiras versões da biblioteca.
Veredito
Times que já apostaram em Formik leem essa comparação de um jeito; times greenfield, de outro. A resposta muda conforme o ponto de partida, não conforme qual biblioteca é "melhor". Se o time quer que TypeScript seja tratado como fonte de verdade, com campos obrigatórios por padrão e menor risco de divergência silenciosa entre schema e tipo, Zod tende a ser a escolha mais segura para projetos novos. Se o projeto já roda sobre Formik ou tem uma base de schemas Yup estabelecida, ou se a semântica de "opcional por padrão, obrigatório quando necessário" encaixa melhor no domínio (formulários com campos condicionais), Yup continua sendo uma opção madura e bem documentada.
Essa inversão de padrão é também o erro mais comum em migrações mal revisadas entre as duas: um .required() esquecido, ou um .optional() que devia estar lá, passa despercebido até um payload incompleto chegar em produção. Revisar campo a campo continua sendo o único jeito seguro de trocar uma pela outra. Nenhum script automatizado substitui esse trabalho manual.