The short answer
Write the static keyword; static fields take their type from their value like any field, and static methods take @param tags like any method:
export class Order {
static TAX_RATE = 0.2;
static #nextNumber = 1001;
/**
* @param {string} id
* @param {number} subtotal Cents.
*/
constructor(id, subtotal) {
this.id = id;
this.subtotal = subtotal;
}
get total() {
return Math.round(this.subtotal * (1 + Order.TAX_RATE));
}
/** @param {number[]} lineTotals */
static fromLines(lineTotals) {
const id = `A-${Order.#nextNumber++}`;
return new Order(id, lineTotals.reduce((sum, cents) => sum + cents, 0));
}
}
const order = Order.fromLines([3900, 1250]);
console.log(order.id, order.total);export class Order {
static TAX_RATE = 0.2;
static #nextNumber = 1001;
id: string;
subtotal: number;
constructor(id: string, subtotal: number) {
this.id = id;
this.subtotal = subtotal;
}
get total(): number {
return Math.round(this.subtotal * (1 + Order.TAX_RATE));
}
static fromLines(lineTotals: number[]): Order {
const id = `A-${Order.#nextNumber++}`;
return new Order(id, lineTotals.reduce((sum, cents) => sum + cents, 0));
}
}
const order = Order.fromLines([3900, 1250]);
console.log(order.id, order.total);jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Order.TAX_RATE is a number, Order.#nextNumber is a counter only the class can see, and Order.fromLines returns an Order, inferred from its return new Order(…).
Why it works
Static members belong to the class object, not to its instances, and TypeScript types them that way: they live on typeof Order, the type of the class itself, while Order names the type of an instance. Inside a static method, this is the class too, so this.TAX_RATE would also work there.
The same thing in TypeScript
The TypeScript equivalent tab is the same class with the instance fields id and subtotal declared and annotated. The static members need no change at all: static is JavaScript syntax.
What goes wrong
Reading a static member through an instance fails, and tsc says where it lives (TS2576). The reverse, an instance property read through the class, is TS2339:
export class Order {
static TAX_RATE = 0.2;
/** @param {string} id */
constructor(id) {
this.id = id;
}
}
const order = new Order("A-1001");
console.log(order.TAX_RATE);error TS2576: Property 'TAX_RATE' does not exist on type 'Order'. Did you mean to access the static member 'Order.TAX_RATE' instead?
console.log(Order.id);error TS2339: Property 'id' does not exist on type 'typeof Order'.jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Gotchas
@staticis ignored. The tag does not make anything static; TypeScript does not read it.- Static blocks work. A
static { … }block runs once when the class is defined, and tsc checks its body like any other code. - Static members are inherited. A subclass sees
TAX_RATEas its own static property, and can redefine it with a compatible type.
Questions
Does the @static JSDoc tag make a method static?
No. TypeScript ignores @static; only the static keyword makes a member static. A method tagged @static without the keyword is still an instance method, as the @static page shows.
How do I type the class itself, for a function that takes it as an argument?
Use typeof Order: @param {typeof Order} OrderClass accepts the class, with its static members and its constructor. Order alone means an instance.
Sources
- Classes (static members) · TypeScript handbook
- static · MDN Web Docs
Last reviewed 21 October 2026.