@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
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.
}
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /** @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 };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /**
* @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 };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.freezeis the run-time version, and TypeScript types its result asReadonly<T>. readonlyon a property does not make the object it holds read-only:this.tags.push("x")is still allowed on a@readonlyarray property. Type itreadonly string[]for that.- The
readonlykeyword is allowed inside JSDoc types but not in JavaScript code;readonly id = 1in 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
- TypeScript 3.8: JSDoc property modifiers · TypeScript release notes
- JSDoc Reference: @readonly · TypeScript handbook
- @readonly · JSDoc
Last reviewed 20 October 2026.