JSDoc tag

@readonly

  • Checked by TypeScript
  • Since TypeScript 3.8

@readonly on a class property allows assignments only where the property is initialized: the field declaration and the constructor. Since TypeScript 3.8, an assignment anywhere else is an error (TS2540, "Cannot assign to 'id' because it is a read-only property"). It is the JavaScript spelling of TypeScript's readonly modifier.

In a @typedef, write readonly inside the type instead, {{ readonly id: string }}, or wrap the type in Readonly<T>. A @readonly line among @property lines does something unexpected, shown in the third example.

At a glance

Use JSDoc in a .js file TypeScript in a .ts file
A read-only class property /** @readonly */ createdAt = new Date(); readonly createdAt = new Date();
A read-only typedef property /** @typedef {{ readonly id: string }} Order */ type Order = { readonly id: string };

Examples

order.js order.ts Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: order.js(13,10): error TS2540: Cannot assign to 'id' because it is a read-only property. order.ts(11,10): error TS2540: Cannot assign to 'id' because it is a read-only property. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. order.js(13,10): error TS2540: Cannot assign to 'id' because it is a read-only property. order.ts(11,10): error TS2540: Cannot assign to 'id' because it is a read-only property. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. order.js(13,10): error TS2540: Cannot assign to 'id' because it is a read-only property. order.ts(11,10): error TS2540: Cannot assign to 'id' because it is a read-only property. checked 21 October 2026
export class Order {
  /** @readonly */
  createdAt = new Date();

  /** @param {string} id */
  constructor(id) {
    /** @readonly */
    this.id = id;
  }

  rename() {
    this.id = "new-id";error TS2540: Cannot assign to 'id' because it is a read-only property.
  }
}
export class Order {
  readonly createdAt = new Date();
  readonly id: string;

  constructor(id: string) {
    this.id = id;
  }

  rename() {
    this.id = "new-id";error TS2540: Cannot assign to 'id' because it is a read-only property.
  }
}
A @readonly property can only be set while the object is built.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}
order.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: order.js(8,7): error TS2540: Cannot assign to 'id' because it is a read-only property. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. order.js(8,7): error TS2540: Cannot assign to 'id' because it is a read-only property. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. order.js(8,7): error TS2540: Cannot assign to 'id' because it is a read-only property. checked 21 October 2026
/** @typedef {{ readonly id: string, total: number }} Order */

/** @type {Order} */
const order = { id: "A-100", total: 42 };

order.total = 0;
order.id = "A-101";error TS2540: Cannot assign to 'id' because it is a read-only property.

export { order };
In a @typedef, write readonly in the type instead.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}
order.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: order.js(10,30): error TS2353: Object literal may only specify known properties, and 'total' does not exist in type 'Order'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. order.js(10,30): error TS2353: Object literal may only specify known properties, and 'total' does not exist in type 'Order'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. order.js(10,30): error TS2353: Object literal may only specify known properties, and 'total' does not exist in type 'Order'. checked 21 October 2026
/**
 * @typedef {object} Order
 * @property {string} id
 * @readonly
 * @property {number} total
 */

/** @type {Order} */
const order = { id: "A-100", total: 42 };error TS2353: Object literal may only specify known properties, and 'total' does not exist in type 'Order'.

export { order };
A @readonly line inside a @typedef ends its property list.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

In classes

Tag a field declaration, or a this.id = id assignment in the constructor, as in the first example. Both are read-only afterwards. Methods, including the class's own, cannot assign to them.

In @typedef blocks

The tag does not apply to a single @property line. Worse, a @readonly line in the middle of a @typedef block ends the type's property list: in the third example total comes after it and is dropped from Order, so the object literal that sets total fails with TS2353. Use the readonly keyword inside a type literal, or Readonly<Order> where the whole object should be fixed.

Gotchas

  • Read-only is a compile-time check. Object.freeze is the run-time version, and TypeScript types its result as Readonly<T>.
  • readonly on a property does not make the object it holds read-only: this.tags.push("x") is still allowed on a @readonly array property. Type it readonly string[] for that.
  • The readonly keyword is allowed inside JSDoc types but not in JavaScript code; readonly id = 1 in a class body is TS8009.

Questions

How do I make a property read-only in JSDoc?

On a class property, /** @readonly */ above it. In a type, readonly before the property name inside the braces, or Readonly<T> for every property at once.

Sources

Last reviewed 20 October 2026.