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.
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).
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: "[email protected]" };
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: "[email protected]" };
// Don't mutate; create a new object
const updatedUser: User = {
...user,
email: "[email protected]",
};
// user.email is still "[email protected]" (unchanged)
// updatedUser.email is "[email protected]" (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.
### 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.
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript