Uma propriedade opcional em TypeScript parece simples: escreva timeoutMs?: number e siga em frente. O problema aparece quando um objeto é montado com spreads ou quando uma API interpreta "chave ausente" como "use o padrão". Nesse caso, {} e { timeoutMs: undefined } não são o mesmo objeto.
Com exactOptionalPropertyTypes: true, o compilador deixa essa diferença explícita. timeoutMs?: number permite omitir a chave ou enviar um número. Se o contrato também aceita uma chave presente com undefined, escreva timeoutMs?: number | undefined. A opção ajuda quando a presença da chave tem significado, mas não substitui validação em runtime.
Este artigo é um spoke de validação de tool calls em TypeScript. O owner trata dados externos que chegam para uma tool. Aqui, a pergunta é anterior: qual objeto o código pode construir e qual significado cada estado deve ter?

Resposta curta
- Use
prop?: Tquando a chave puder ser omitida e, quando presente, precisar conterT.- Use
prop?: T | undefinedquando uma chave presente comundefinedfor um estado válido.- Ative
exactOptionalPropertyTypesquando o código distingue ausência,undefinede, talvez,null.- Revise objetos construídos com spread: passar
undefinedpara representar "não informado" pode deixar uma chave presente.- Continue validando JSON, respostas HTTP e argumentos de tools em runtime.
O que exactOptionalPropertyTypes muda?
Sem a opção, o TypeScript trata uma propriedade opcional como se o tipo também aceitasse undefined em muitas atribuições. Assim, este código costuma compilar:
interface UserPreferences {
colorTheme?: "dark" | "light";
}
const preferences: UserPreferences = {
colorTheme: undefined,
};
Com "exactOptionalPropertyTypes": true, a declaração é lida de forma mais literal. colorTheme?: "dark" | "light" significa que a chave pode não existir ou pode conter uma das duas strings. Atribuir undefined passa a ser um erro, a menos que undefined apareça explicitamente no tipo.
A referência do TSConfig para exactOptionalPropertyTypes usa o mesmo contraste: a ausência representa um estado, enquanto uma chave com undefined continua sendo uma chave. As notas do TypeScript 4.4 também explicam que a opção não faz parte da família strict e depende de strictNullChecks.
Isso muda principalmente o que o código pode escrever. Ao ler preferences.colorTheme, você ainda pode obter "dark" | "light" | undefined, porque a chave pode estar ausente. A opção não faz a leitura deixar de ser opcional.
Por que a chave ausente é diferente de undefined?
JavaScript permite representar os dois estados:
const omitted = {};
const present = { timeoutMs: undefined };
console.log("timeoutMs" in omitted); // false
console.log("timeoutMs" in present); // true
console.log(Object.keys(omitted)); // []
console.log(Object.keys(present)); // ["timeoutMs"]
O operador in verifica se a propriedade existe no objeto ou na cadeia de protótipos. A documentação do MDN também destaca que uma propriedade pode existir e ter valor undefined; por isso, "x" in object não é a mesma coisa que object.x !== undefined.
Essa diferença aparece em código que mescla opções. Imagine um executor que mantém um padrão quando timeoutMs não foi informado:
type ExecutorOptions = {
timeoutMs?: number;
};
const defaults = { timeoutMs: 10_000 };
const incoming: ExecutorOptions = { timeoutMs: undefined };
const options = { ...defaults, ...incoming };
console.log(options.timeoutMs); // undefined
O spread não sabe que undefined queria dizer "não altere o padrão". Ele copia a propriedade presente. Object.assign() segue a mesma ideia: copia propriedades próprias enumeráveis e as fontes posteriores sobrescrevem chaves anteriores (MDN, Object.assign()).
O TypeScript FAQ usa um exemplo semelhante para mostrar por que Partial<T> com valores undefined pode ser suspeito quando combinado com spread (TypeScript FAQ). Isso não torna todo spread errado. O tipo ainda precisa dizer se a presença da chave importa.
Como modelar configurações e opções?
Comece escrevendo o contrato antes de ativar a flag. Há pelo menos três intenções diferentes.
A chave pode ser omitida, mas não pode ser undefined
Use uma propriedade opcional comum quando o chamador pode deixar o valor de fora:
type ToolOptions = {
timeoutMs?: number;
signal?: AbortSignal;
};
const defaults: ToolOptions = {};
const withTimeout: ToolOptions = { timeoutMs: 5_000 };
Com exactOptionalPropertyTypes, const invalid: ToolOptions = { timeoutMs: undefined } falha. Esse erro é útil quando o executor distingue uma opção não fornecida de uma opção explicitamente configurada.
A chave presente com undefined é válida
Algumas APIs usam a presença da chave para padronizar genéricos, compor adapters ou preservar uma assinatura pública. Nesse caso, declare o estado de forma explícita:
type PublicOptions = {
timeoutMs?: number | undefined;
};
const allowed: PublicOptions = { timeoutMs: undefined };
O ? ainda permite omitir a chave. O | undefined acrescenta a possibilidade de a chave existir com esse valor. Não adicione essa união apenas para silenciar o compilador: ela documenta uma escolha do contrato.
null representa uma operação diferente
Em um payload de atualização, talvez a ausência signifique "não alterar" e null signifique "limpar":
type ProfilePatch = {
displayName?: string;
bio?: string | null;
};
const rename: ProfilePatch = { displayName: "Ada" };
const clearBio: ProfilePatch = { bio: null };
Esse significado é uma decisão da sua API. exactOptionalPropertyTypes ajuda a impedir que undefined entre por acidente, mas não escolhe se null deve limpar um campo. Documente a regra e teste os estados aceitos no limite que recebe o payload.
Para um registro de ferramentas escrito no código, use satisfies para conferir a configuração sem perder inferência. São verificações complementares: satisfies confere a forma estática do objeto, enquanto exactOptionalPropertyTypes evita tratar uma chave presente com undefined como se ela estivesse ausente.
Como ativar a opção sem transformar a migração em palpite?
Ative-a junto com strictNullChecks, como no tsconfig.json mínimo abaixo:
{
"compilerOptions": {
"strict": true,
"strictNullChecks": true,
"exactOptionalPropertyTypes": true,
"noEmit": true
}
}
O comando de verificação pode ser simples:
npx tsc --noEmit
Para proteger um comportamento específico, use um teste de tipo com @ts-expect-error:
type RequestOptions = {
cache?: "no-store" | "force-cache";
};
const request: RequestOptions = {};
request.cache = "no-store";
// @ts-expect-error: o contrato exige ausência ou um modo conhecido
request.cache = undefined;
Uma migração segura separa quatro perguntas:
- O erro apareceu porque o código queria omitir a chave? Construa o objeto condicionalmente.
- O erro apareceu porque
undefinedé um estado público válido? Adicione| undefinedà declaração que expressa essa intenção. - O erro veio de uma dependência ou de tipos gerados? Confirme a versão, abra o tipo que originou a incompatibilidade e não esconda o problema com uma asserção global.
- O erro veio de um valor externo? Não resolva com uma alteração de interface. Valide o dado antes de convertê-lo em um tipo interno.
Para a primeira pergunta, prefira não inserir a chave quando a condição não existe:
const options: RequestOptions = {
...(useCache ? { cache: "force-cache" } : {}),
};
O guia de validação de JSON externo em TypeScript cobre a fronteira diferente: um tipo estático não confirma que o JSON recebido tem a forma declarada.
O que a flag não prova?
exactOptionalPropertyTypes age na checagem de atribuições do TypeScript. Ela não inspeciona uma resposta HTTP, não valida uma mensagem de fila e não impede que any ou um cast incorreto atravesse o programa. Também não garante que a implementação respeite o significado documentado de null.
Em uma chamada de tool, por exemplo, ainda é necessário validar os argumentos recebidos antes da execução. A validação de tool calls em TypeScript trata esse contrato de runtime. O compilador pode impedir que o seu próprio código construa um objeto inválido; ele não pode conferir sozinho um objeto criado fora do processo.
Também existe um custo de compatibilidade. A flag afeta todas as propriedades opcionais do projeto, não apenas uma interface. Tipos públicos, código gerado e dependências podem assumir que prop?: T aceita undefined. A FAQ do TypeScript recomenda entender esse impacto no contexto do projeto. Não há uma configuração por tipo que permita ativar a semântica apenas em uma interface isolada.
Checklist de decisão
Antes de ativar a opção, responda:
- A ausência da chave significa "usar padrão", "herdar" ou "não alterar"?
- Uma chave presente com
undefinedtem algum comportamento diferente? nullé o valor de limpeza ou deve ser rejeitado?- Algum spread ou
Object.assign()copia opções que carregamundefined? - As declarações públicas e os tipos gerados aceitam a semântica que você quer?
- O código ainda valida entradas externas em runtime?
- Existe um teste de compilação que falha se a distinção voltar a ser apagada?
Se a resposta para a primeira pergunta for sim e undefined não tiver significado próprio, exactOptionalPropertyTypes é uma forma direta de fazer o tipo refletir o contrato. Se a resposta for não, manter a flag desligada pode ser coerente, desde que essa flexibilidade seja intencional e esteja coberta por testes.
Método e limites
Samuel Fajreldines é o responsável por este artigo. A pesquisa comparou a referência atual do TSConfig, as notas do TypeScript 4.4, a FAQ do projeto e a documentação do MDN sobre presença de propriedades e Object.assign(). A fixture mínima foi compilada com uma instalação temporária do TypeScript e o exemplo de presença foi executado com Node.js. Os exemplos demonstram o contrato descrito, mas não são um benchmark de migração nem uma medição de compatibilidade do ecossistema.
Fontes
- TypeScript,
exactOptionalPropertyTypes, consultado em 2026-11-09. - TypeScript, notas da versão 4.4, consultadas em 2026-11-09.
- TypeScript, FAQ: Require Properties to Either Be Missing or Not
undefined, consultado em 2026-11-09. - MDN, operador
in, consultado em 2026-11-09. - MDN,
Object.assign(), consultado em 2026-11-09.