An optional property, @property {string} [nickname] in JSDoc, normally accepts undefined as a value as well as being left out. exactOptionalPropertyTypes makes it mean only "may be left out": writing nickname: undefined becomes an error (TS2375) unless the type says string | undefined.
The difference is visible at run time: "nickname" in user and Object.keys(user) see a property set to undefined but not a missing one. The option arrived with TypeScript 4.4, is not part of strict, and needs strictNullChecks.
At a glance
| Write | "exactOptionalPropertyTypes": true if your code tells "missing" from "set to undefined" (with in or Object.keys); otherwise leave it out |
|---|---|
| On the command line | --exactOptionalPropertyTypes |
| Type | boolean |
| Default in TypeScript 5.9 | false |
| TypeScript's description | Interpret optional property types as written, rather than adding 'undefined'. |
| Generator, relaxed | not written |
| Generator, recommended | not written |
| Generator, strict | true |
The description is quoted from TypeScript's own option list (Apache-2.0); see the notices. The generator rows are what the jsconfig generator writes at each level.
Examples
/**
* @typedef {object} User
* @property {string} name
* @property {string} [nickname]
*/
/** @type {User} */
export const user = { name: "Ada", nickname: undefined };error TS2375: Type '{ name: string; nickname: undefined; }' is not assignable to type 'User' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
Types of property 'nickname' are incompatible.
Type 'undefined' is not assignable to type 'string'.jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": [],
"exactOptionalPropertyTypes": true
},
"include": [
"*.js"
]
} /**
* @typedef {object} User
* @property {string} name
* @property {string} [nickname]
*/
/** @type {User} */
export const user = { name: "Ada", nickname: undefined };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": [
"*.js"
]
} When undefined is a real value
Say so in the type: @property {string | undefined} [nickname] accepts both a missing property and one set to undefined. Use it where your code writes undefined on purpose, for example to clear a field.
Gotchas
- Spreading options objects often writes
undefinedvalues ({ ...defaults, nickname: input.nickname }), so turning this on in an existing codebase can report many lines that are harmless in practice. - Library types are read with the same rule: passing
undefinedfor an optional property of a library's type becomes an error unless that type includesundefined.
Sources
- TSConfig Reference: exactOptionalPropertyTypes · TypeScript
- TypeScript 4.4 release notes · TypeScript handbook
Last reviewed 20 October 2026.