Put @template T in the class's JSDoc comment, and T is available in every member's tags: field types, @param, @returns. It is the JavaScript spelling of class Queue<T>.
JavaScript cannot write new Queue<Job>(), so the type argument comes from one of two places: the variable's @type {Queue<Job>}, as in the first example, or the constructor's arguments, which TypeScript infers it from, as in the second.
At a glance
| Use | JSDoc in a .js file | TypeScript in a .ts file |
|---|---|---|
| A generic class | /** @template T */
class Queue {
/** @param {T} item */
push(item) {}
} | class Queue<T> {
push(item: T) {}
} |
| An instance | /** @type {Queue<Job>} */
const jobs = new Queue(); | const jobs = new Queue<Job>(); |
Examples
/** @template T */
export class Queue {
/** @type {T[]} */
#items = [];
/** @param {T} item */
push(item) {
this.#items.push(item);
}
/** @returns {T | undefined} */
shift() {
return this.#items.shift();
}
}
/** @type {Queue<{ to: string, subject: string }>} */
const outbox = new Queue();
outbox.push({ to: "jane@example.com", subject: "Your order shipped" });
outbox.shift()?.subject;export class Queue<T> {
#items: T[] = [];
push(item: T) {
this.#items.push(item);
}
shift(): T | undefined {
return this.#items.shift();
}
}
const outbox = new Queue<{ to: string; subject: string }>();
outbox.push({ to: "jane@example.com", subject: "Your order shipped" });
outbox.shift()?.subject;jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /** @template T */
export class Labeled {
/**
* @param {string} label
* @param {T} value
*/
constructor(label, value) {
this.label = label;
this.value = value;
}
}
const price = new Labeled("price", 19.5);
price.value.toFixed(2);
const sku = new Labeled("sku", "TEE-01");
sku.value.toLowerCase();jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /** @template T */
export class Queue {
/** @type {T[]} */
items = [];
/** @param {T} item */
push(item) {
this.items.push(item);
}
}
/** @type {Queue<string>} */
const skus = new Queue();
skus.push("TEE-01");
skus.push(42);error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Without a type argument
If neither the variable nor the constructor's arguments say what T is, it becomes any in a .js file, and the instance checks nothing. Give the variable a type, add a default with @template [T=…], or pass something the constructor can infer from.
Subclasses
A subclass of a generic class supplies the type argument with @extends: /** @extends {Queue<string>} */ class SkuQueue extends Queue {}. A generic subclass combines @template T with @extends {Queue<T>}.
Gotchas
- Put the field's JSDoc on its own line above it; a comment on the same line as the class's opening brace is not attached to the member.
- Static members cannot use the class's type parameter, as in TypeScript.
Questions
How do I make a JavaScript class generic with JSDoc?
Add @template T to the class comment and use T in the members' JSDoc. Create instances with a typed variable, /** @type {Queue<string>} */ const q = new Queue();.
Sources
- Generics: generic classes · TypeScript handbook
- JSDoc Reference: @template · TypeScript handbook
Last reviewed 20 October 2026.