How to fix Cannot assign to 'X' because it is a read-only property in TypeScript
This TypeScript error occurs when you try to modify a property marked with the readonly modifier. The fix involves either removing the readonly modifier, creating a new object instead of mutating, or using a type utility to strip readonly from properties.
Quick fix
Fixes TS2540
interface User {
readonly id: number;
readonly name: string;
email: string;
}
const user: User = { id: 1, name: "John", email: "john@example.com" };
user.id = 2; // Error: Cannot assign to 'id' because it is a read-only property- Identify the readonly property
- Remove the readonly modifier (if appropriate)
- Create a new object instead of mutating (immutable approach)
- Use a Mutable utility type to strip readonly
interface User {readonly id: number;readonly name: string;email: string;}const user: User = { id: 1, name: "John", email: "john@example.com" };user.id = 2; // Error: Cannot assign to 'id' because it is a read-only propertyCannot assign to 'X' because it is a read-only property
What this error means
In TypeScript, the `readonly` keyword restricts properties from being assigned after their initial declaration. This is a compile-time safety feature that helps prevent unintended mutations. When you mark a property as `readonly`, TypeScript enforces that it can only be assigned: 1. At the point of declaration (inline) 2. In the class constructor 3. Never afterwards in instance methods or external code If you attempt to assign a new value to a readonly property anywhere else, TypeScript will emit error TS2540. This is a type-checking error only—it doesn't affect runtime behavior since JavaScript doesn't have a true readonly concept. Once compiled to JavaScript, the code will actually allow the assignment (which can lead to subtle bugs if you rely on readonly for immutability).
Typical symptoms
- TypeScript compiler shows 'Cannot assign to X because it is a read-only property'
- You're trying to modify an object property after it was created
- Error occurs in class instance methods or when mutating objects outside the class
- IDE shows red squiggly line under the assignment
- The error mentions a specific property name that you're trying to assign
- Code compiles and runs fine in JavaScript but fails TypeScript type checking
Common causes
- Property is declared with the readonly modifier (most common)
- Property comes from a readonly interface or type
- Class property is declared as readonly and you're trying to modify it outside the constructor
- You're trying to mutate a readonly object literal created with as const
- Inherited readonly properties from a parent class are being modified
- Using a third-party type definition with readonly constraints
How to fix "Cannot assign to 'X' because it is a read-only property"
First, locate which property is marked as readonly in your code. Look for the readonly keyword:
interface User {
readonly id: number;
readonly name: string;
email: string;
}
const user: User = { id: 1, name: "John", email: "john@example.com" };
user.id = 2; // Error: Cannot assign to 'id' because it is a read-only propertyIn a class:
class Config {
readonly apiUrl: string;
constructor(url: string) {
this.apiUrl = url; // OK: assignment in constructor
}
}
const config = new Config("https://api.example.com");
config.apiUrl = "https://new-api.com"; // Error: Cannot assignIf you genuinely need to mutate the property, simply remove the readonly keyword:
// Before
interface User {
readonly id: number;
readonly name: string;
}
// After
interface User {
id: number; // readable and writable
name: string;
}
const user: User = { id: 1, name: "John" };
user.id = 2; // Now OKNote: Only do this if the property doesn't need immutability protection.
Instead of modifying the readonly property, create a new object with updated values:
interface User {
readonly id: number;
readonly name: string;
readonly email: string;
}
const user: User = { id: 1, name: "John", email: "john@example.com" };
// Don't mutate; create a new object
const updatedUser: User = {
...user,
email: "john.new@example.com",
};
// user.email is still "john@example.com" (unchanged)
// updatedUser.email is "john.new@example.com" (new object)This approach is immutable and aligns with modern JavaScript practices. It's also TypeScript's recommended pattern for data transformations.
If you need to mutate a readonly type, create a utility type that removes the readonly modifier:
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
interface User {
readonly id: number;
readonly name: string;
}
// Create a mutable version
const user: Mutable<User> = { id: 1, name: "John" };
user.id = 2; // Now OK
user.name = "Jane"; // Now OKThis is useful when working with APIs that return readonly types but you need to modify them locally. The -readonly mapped type modifier strips the readonly constraint.
If you absolutely must bypass TypeScript's readonly check, use a type assertion (but be careful):
interface User {
readonly id: number;
readonly name: string;
}
const user: User = { id: 1, name: "John" };
// Type assertion bypasses the readonly check
(user as { id: number; name: string }).id = 2;
// Or more directly:
(user as any).id = 2;Caution: This bypasses TypeScript's safety checks. Only use when you're certain of what you're doing and there's no better alternative. This approach negates the benefits of the readonly modifier.
If you're working with a class and need to set a readonly property, ensure you do it in the constructor:
class Config {
readonly apiUrl: string;
readonly timeout: number;
constructor(url: string, timeout: number) {
// These assignments in the constructor are allowed
this.apiUrl = url;
this.timeout = timeout;
}
updateUrl(newUrl: string) {
// This would error:
// this.apiUrl = newUrl;
// Instead, create a new instance:
return new Config(newUrl, this.timeout);
}
}Readonly properties in classes can only be assigned in the constructor.
Advanced notes
### Understanding Readonly at Runtime
The readonly modifier is purely a TypeScript compile-time check. Once your TypeScript code compiles to JavaScript, there is no runtime enforcement of readonly constraints:
// TypeScript (with readonly)
interface User {
readonly id: number;
}
const user: User = { id: 1 };
user.id = 2; // TS2540 error// Compiled JavaScript (no readonly concept exists)
const user = { id: 1 };
user.id = 2; // Actually runs fine!This is why using type assertions to bypass readonly can be dangerous—TypeScript won't prevent it at compile time, but your code will still mutate the object at runtime, potentially breaking logic that depends on immutability.
### Readonly Arrays and Tuples
The readonly modifier also applies to array types:
const readonlyArray: readonly string[] = ["a", "b", "c"];
readonlyArray.push("d"); // Error: Property 'push' does not exist on type 'readonly string[]'
const readonlyTuple: readonly [number, string] = [1, "a"];
readonlyTuple[0] = 2; // Error: Cannot assign to '0' because it is a read-only propertyTo work with readonly arrays, you must create new arrays instead of mutating them.
### Readonly vs Object.freeze()
Don't confuse TypeScript's readonly with JavaScript's Object.freeze():
- readonly: TypeScript compile-time check; no runtime effect
- Object.freeze(): Runtime enforcement; makes objects truly immutable
For true immutability, combine both:
interface User {
readonly id: number;
readonly name: string;
}
const user: Readonly<User> = Object.freeze({ id: 1, name: "John" });
// Now it's both type-safe (readonly) and runtime-protected (frozen)### Generic Readonly Utilities
TypeScript's built-in utilities include:
- Readonly<T>: Makes all properties readonly
- ReadonlyArray<T>: Creates readonly arrays
- ReadonlyMap, ReadonlySet: Collections with readonly interfaces
Use these when designing APIs that need immutability guarantees.
Frequently asked questions
- What does "TS2540" mean?
- In TypeScript, the readonly keyword restricts properties from being assigned after their initial declaration. This is a compile-time safety feature that helps prevent unintended mutations. When you mark a property as readonly, TypeScript enforces that it can only be assigned: 1.
- How do I fix "TS2540"?
- This TypeScript error occurs when you try to modify a property marked with the readonly modifier. The fix involves either removing the readonly modifier, creating a new object instead of mutating, or using a type utility to strip readonly from properties.
- What causes "TS2540"?
- Property is declared with the readonly modifier (most common) Property comes from a readonly interface or type Class property is declared as readonly and you're trying to modify it outside the constructor
- Is the "TS2540" error serious?
- It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.