@template T declares a type parameter named T for the function, class, @typedef or @callback it belongs to. Inside the same comment, and in the body, T can be used like any type. It is the JavaScript spelling of <T> in TypeScript.
Callers never write the type argument; JavaScript has no syntax for it. TypeScript infers T from the arguments at each call, so first(["S", "M"]) returns string | undefined and first([19.5]) returns number | undefined.
At a glance
| Use | JSDoc in a .js file | TypeScript in a .ts file |
|---|---|---|
| A type parameter | /**
* @template T
* @param {T[]} items
* @returns {T | undefined}
*/ | function first<T>(items: T[]): T | undefined {} |
| With a constraint | /** @template {{ id: string }} T */ | <T extends { id: string }> |
| With a default | /** @template [T=unknown] */ | <T = unknown> |
| Several parameters | /** @template K, V */ | <K, V> |
Examples
/**
* @template T
* @param {T[]} items
* @returns {T | undefined}
*/
export function first(items) {
return items[0];
}
const size = first(["S", "M", "L"]);
const price = first([19.5, 12]);
size?.toLowerCase();
price?.toFixed(2);export function first<T>(items: T[]): T | undefined {
return items[0];
}
const size = first(["S", "M", "L"]);
const price = first([19.5, 12]);
size?.toLowerCase();
price?.toFixed(2);jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /**
* @template {{ id: string }} T
* @param {T[]} items
* @returns {Map<string, T>}
*/
export function byId(items) {
return new Map(items.map((item) => [item.id, item]));
}
byId([{ id: "TEE-01", price: 19.5 }]);
byId([{ sku: "TEE-01" }]);error TS2353: Object literal may only specify known properties, and 'sku' does not exist in type '{ id: string; }'.export function byId<T extends { id: string }>(items: T[]): Map<string, T> {
return new Map(items.map((item) => [item.id, item]));
}
byId([{ id: "TEE-01", price: 19.5 }]);
byId([{ sku: "TEE-01" }]);error TS2353: Object literal may only specify known properties, and 'sku' does not exist in type '{ id: string; }'.jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /**
* @template [T=unknown]
* @typedef {{ ok: true, data: T } | { ok: false, error: string }} Result
*/
/** @type {Result<number>} */
const count = { ok: true, data: 3 };
/** @type {Result} */
const anything = { ok: true, data: "whatever" };
/** @param {Result<number>} result */
export function show(result) {
return result.ok ? result.data.toFixed(0) : result.error;
}
show(count);
export { anything };type Result<T = unknown> = { ok: true; data: T } | { ok: false; error: string };
const count: Result<number> = { ok: true, data: 3 };
const anything: Result = { ok: true, data: "whatever" };
export function show(result: Result<number>) {
return result.ok ? result.data.toFixed(0) : result.error;
}
show(count);
export { anything };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Constraints
A type in braces before the name constrains the parameter: @template {{ id: string }} T accepts any type with a string id. Arguments that do not fit are errors, and inside the function you can use item.id safely.
A constraint applies only to the first name in a list. In @template {string} K, V, K must be a string and V is unconstrained; give each constrained parameter its own @template line.
Defaults
Brackets with an equals sign give a default: @template [T=unknown]. TypeScript 4.5 added defaults in JSDoc. They matter on a generic @typedef or class, where Result written without arguments then means Result<unknown>, as in the third example. A constraint and a default can be combined: @template {string | number} [T=number].
Classes and typedefs
On a class, @template T above the class makes T available to every member's JSDoc, and new Box("x") infers it from the constructor's parameters. On a @typedef or @callback, put @template first in the same comment.
Gotchas
- A type parameter that appears only in the return type cannot be inferred and falls back to its constraint or
unknown. Make it appear in a parameter, or type the result at the call site. - Writing
first<string>(items)in a.jsfile does not pass a type argument: JavaScript reads it as two comparisons, and tsc reports errors about the<and>operators. - Generic type parameter declarations,
function first<T>(), are TypeScript syntax and an error in a.jsfile (TS8004).
Questions
How do I write generics in JSDoc?
Declare the type parameter with @template T in the function's comment and use T in its @param and @returns types. TypeScript infers it at each call.
Sources
- JSDoc Reference: @template · TypeScript handbook
- TypeScript 4.5: const assertions and default type arguments in JSDoc · TypeScript release notes
- Generics · TypeScript handbook
Last reviewed 20 October 2026.