A conditional type, T extends string ? "text" : "other", checks whether T is assignable to string and becomes the first type if it is and the second if not. In JSDoc it goes in a @typedef with @template T, and it is written exactly as in TypeScript. TypeScript added conditional types in version 2.8.
Most code uses conditional types through the built-in utility types (Exclude, NonNullable, ReturnType, Awaited) rather than writing them. Reading one is still useful, because error messages and library types show them.
At a glance
| Use | JSDoc in a .js file | TypeScript in a .ts file |
|---|---|---|
| A conditional type | /**
* @template T
* @typedef {T extends string ? "text" : "other"} Kind
*/ | type Kind<T> = T extends string ? "text" : "other"; |
| With infer | /**
* @template T
* @typedef {T extends Promise<infer U> ? U : T} Unwrapped
*/ | type Unwrapped<T> = T extends Promise<infer U> ? U : T; |
Examples
/**
* @template T
* @typedef {T extends string ? "text" : T extends number ? "number" : "other"} Kind
*/
/** @type {Kind<string>} */
const a = "text";
/** @type {Kind<42>} */
const b = "number";
/**
* @template T
* @typedef {T extends Promise<infer U> ? U : T} Unwrapped
*/
/** @type {Unwrapped<Promise<string[]>>} */
const skus = ["TEE-01"];
export { a, b, skus };type Kind<T> = T extends string ? "text" : T extends number ? "number" : "other";
const a: Kind<string> = "text";
const b: Kind<42> = "number";
type Unwrapped<T> = T extends Promise<infer U> ? U : T;
const skus: Unwrapped<Promise<string[]>> = ["TEE-01"];
export { a, b, skus };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /**
* @template T
* @typedef {T extends string ? "text" : "other"} Kind
*/
/** @type {Kind<boolean>} */
const k = "text";error TS2322: Type '"text"' is not assignable to type '"other"'.
export { k };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /**
* @template T
* @typedef {T extends null | undefined ? never : T} Present
*/
/** @typedef {Present<string | null | undefined>} Name */
/** @type {Name} */
const name = "Jane";
/** @type {Name} */
const missing = null;error TS2322: Type 'null' is not assignable to type 'string'.
export { name, missing };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} infer
infer U inside the extends clause names a part of the type for use in the true branch: T extends Promise<infer U> ? U : T pulls the resolved type out of a promise. That is the idea behind Awaited and ReturnType.
Conditional types and unions
When the checked type is a bare type parameter and the argument is a union, the condition applies to each member separately and the results are joined. Present<string | null | undefined> in the third example becomes string, because the null and undefined members map to never. This is how NonNullable and Exclude work.
Gotchas
- Wrap the type parameter in brackets,
[T] extends [string], to test a union as a whole instead of member by member. - Deeply nested conditional types slow down checking and produce long messages; prefer a built-in utility type when one fits.
Questions
Can I write conditional types in JSDoc?
Yes. Put them in a @typedef with the @template parameters they use; the syntax inside the braces is TypeScript's.
Sources
- Conditional Types · TypeScript handbook
- TypeScript 2.8: conditional types · TypeScript release notes
Last reviewed 20 October 2026.