This TypeScript error occurs when you try to write to an index signature marked as readonly. Index signatures allow dynamic property access, and when marked readonly, they prevent assignment to any property accessed via bracket notation. The fix involves either removing the readonly modifier, using mutable types, or creating new objects instead of mutating existing ones.
In TypeScript, index signatures allow you to define types for objects where you don't know all property names ahead of time. When you mark an index signature as `readonly`, you're telling TypeScript that all properties accessed via that index signature pattern should be read-only. The error "Index signature in type 'X' only permits reading" occurs when you try to assign a value to a property through a readonly index signature. This is TypeScript's way of enforcing immutability for dynamically accessed properties. For example, with `readonly [index: number]: string`, you can read array elements but cannot modify them. This applies to both numeric index signatures (arrays) and string index signatures (objects with dynamic keys).
First, locate where the readonly index signature is defined. Look for interfaces or types with readonly [index: type]: valueType:
// Example 1: Readonly array index signature
interface ReadonlyStringArray {
readonly [index: number]: string;
}
const arr: ReadonlyStringArray = ["a", "b", "c"];
arr[0] = "x"; // Error: Index signature in type 'ReadonlyStringArray' only permits reading
// Example 2: Readonly object index signature
interface ReadonlyConfig {
readonly [key: string]: string;
}
const config: ReadonlyConfig = { apiUrl: "https://api.example.com" };
config["apiUrl"] = "https://new-api.com"; // Error: Index signature only permits reading
// Example 3: Using ReadonlyArray<T>
const numbers: ReadonlyArray<number> = [1, 2, 3];
numbers[0] = 10; // Error: Index signature in type 'readonly number[]' only permits readingCheck your type definitions, imported types, and any as const assertions that might create readonly types.
If you need to modify properties accessed via index signatures, remove the readonly keyword:
// Before (readonly - cannot assign)
interface Config {
readonly [key: string]: string;
}
// After (mutable - can assign)
interface Config {
[key: string]: string; // No readonly modifier
}
const config: Config = { apiUrl: "https://api.example.com" };
config["apiUrl"] = "https://new-api.com"; // Now OK
config.newKey = "value"; // Also OK
// For arrays:
// Before: readonly [index: number]: string
// After: [index: number]: stringNote: Only remove readonly if you genuinely need mutability. Readonly index signatures provide valuable immutability guarantees.
For arrays, use regular array types instead of readonly variants:
// Problem: Readonly array
const readonlyNumbers: readonly number[] = [1, 2, 3];
readonlyNumbers[0] = 10; // Error
// Solution 1: Regular array type
const mutableNumbers: number[] = [1, 2, 3];
mutableNumbers[0] = 10; // OK
// Solution 2: Array<T> instead of ReadonlyArray<T>
const numbersArray: Array<number> = [1, 2, 3];
numbersArray[0] = 10; // OK
// Solution 3: Remove ReadonlyArray wrapper
type ReadonlyUserArray = ReadonlyArray<{ id: number; name: string }>;
type UserArray = Array<{ id: number; name: string }>; // Mutable version
const users: UserArray = [{ id: 1, name: "John" }];
users[0].name = "Jane"; // OKRemember that readonly T[] and ReadonlyArray<T> are equivalent and both prevent modification.
Instead of modifying readonly indexed properties, create new objects or arrays:
// For objects with readonly index signatures
interface ReadonlyConfig {
readonly [key: string]: string;
}
const config: ReadonlyConfig = { apiUrl: "https://api.example.com" };
// Don't mutate; create a new object
const updatedConfig: ReadonlyConfig = {
...config,
apiUrl: "https://new-api.com",
};
// For readonly arrays
const readonlyArray: readonly number[] = [1, 2, 3];
// Create new array with modifications
const newArray: readonly number[] = [
10, // New first element
...readonlyArray.slice(1) // Rest of original array
];
// Or use array methods that return new arrays
const doubledArray: readonly number[] = readonlyArray.map(n => n * 2);This immutable approach is often better for predictability and avoids side effects.
Create utility types to remove readonly from index signatures when needed:
// Utility to make any type mutable (removes readonly from properties and index signatures)
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
interface ReadonlyConfig {
readonly [key: string]: string;
readonly apiUrl: string;
}
// Create mutable version
const config: Mutable<ReadonlyConfig> = { apiUrl: "https://api.example.com" };
config["apiUrl"] = "https://new-api.com"; // Now OK
config.newKey = "value"; // Also OK
// For arrays specifically
type MutableArray<T> = T extends readonly (infer U)[] ? U[] : T;
const readonlyArr: readonly number[] = [1, 2, 3];
const mutableArr: MutableArray<typeof readonlyArr> = [...readonlyArr];
mutableArr[0] = 10; // OKThe -readonly mapped type modifier removes readonly constraints from both regular properties and index signatures.
If you must bypass the readonly check temporarily, use type assertions with caution:
interface ReadonlyConfig {
readonly [key: string]: string;
}
const config: ReadonlyConfig = { apiUrl: "https://api.example.com" };
// Type assertion to bypass readonly (use sparingly!)
(config as { [key: string]: string })["apiUrl"] = "https://new-api.com";
// Or for arrays
const readonlyArray: readonly number[] = [1, 2, 3];
(readonlyArray as number[])[0] = 10;
// Even more permissive (not recommended)
(readonlyArray as any)[0] = 10;Important warnings:
1. This bypasses TypeScript's safety checks
2. The code will still run and mutate the object/array
3. Can lead to bugs if other code depends on immutability
4. Only use when you're certain and there's no better alternative
Consider using Object.assign() or spread syntax to create modified copies instead.
The as const assertion creates deeply readonly types, including readonly index signatures:
// as const makes everything readonly
const config = {
apiUrl: "https://api.example.com",
endpoints: ["/users", "/posts"],
} as const;
config.apiUrl = "https://new.com"; // Error: Cannot assign to 'apiUrl' because it is read-only
config["apiUrl"] = "https://new.com"; // Error: Index signature only permits reading
config.endpoints[0] = "/new"; // Error: Index signature in type 'readonly ["/users", "/posts"]' only permits reading
// Solutions:
// 1. Don't use as const if you need mutability
const mutableConfig = {
apiUrl: "https://api.example.com",
endpoints: ["/users", "/posts"],
}; // No as const
mutableConfig.apiUrl = "https://new.com"; // OK
// 2. Create mutable copy when needed
const readonlyConfig = { apiUrl: "https://api.example.com" } as const;
const mutableCopy = { ...readonlyConfig, apiUrl: "https://new.com" };
// 3. Use type utility to remove readonly
type Mutable<T> = { -readonly [P in keyof T]: T[P] };
const mutableVersion: Mutable<typeof readonlyConfig> = { apiUrl: "https://new.com" };Remember: as const is for creating truly immutable constants.
### Understanding Index Signatures
Index signatures in TypeScript come in two main forms:
1. String index signatures: [key: string]: ValueType
- Matches any string key
- All explicitly declared properties must match this type
- Example: { [key: string]: number; x: number; y: number }
2. Numeric index signatures: [index: number]: ValueType
- Matches array indices
- Used by array types
- Example: Array<T> has [index: number]: T
### Readonly Index Signatures vs Readonly Properties
There's an important distinction:
- Readonly property: readonly prop: string - only affects that specific property
- Readonly index signature: readonly [key: string]: string - affects ALL properties accessed via that index pattern
interface Example {
readonly x: string; // Only x is readonly
readonly [key: string]: string; // ALL string-keyed properties are readonly
}
const obj: Example = { x: "a", y: "b", z: "c" };
obj.x = "new"; // Error: x is readonly property
obj["y"] = "new"; // Error: Index signature only permits reading
obj.z = "new"; // Error: Index signature only permits reading### Runtime Implications
Like regular readonly properties, readonly index signatures are compile-time only. JavaScript has no concept of readonly index signatures:
// TypeScript (compile error)
interface ReadonlyObj {
readonly [key: string]: string;
}
const obj: ReadonlyObj = { x: "a" };
obj["x"] = "b"; // TS2542: Index signature only permits reading// Compiled JavaScript (runs fine)
const obj = { x: "a" };
obj["x"] = "b"; // Actually works!This is why type assertions can be dangerous—they bypass TypeScript's checks but the mutation still happens at runtime.
### Built-in Readonly Types
TypeScript provides several built-in readonly types:
- Readonly<T>: Makes all properties (including index signatures) readonly
- ReadonlyArray<T>: Array with readonly [index: number]: T
- ReadonlyMap<K, V> and ReadonlySet<T>: Immutable collections
- readonly T[]: Shorthand for ReadonlyArray<T>
### Performance Considerations
While readonly index signatures provide safety, they don't affect runtime performance. The TypeScript compiler removes all readonly annotations during compilation.
### When to Use Readonly Index Signatures
Use readonly index signatures when:
1. Working with configuration objects that shouldn't change
2. Creating immutable data structures
3. Designing APIs that return frozen/immutable data
4. Preventing accidental mutations in shared state
5. Working with functional programming patterns
Avoid readonly index signatures when:
1. You need to modify object properties dynamically
2. Working with mutable state management
3. Performance-critical code where object creation is expensive
4. Legacy code that relies on mutation patterns
### Migration Strategy
If you're converting existing code to use readonly index signatures:
1. Start with readonly [key: string]: any to catch all assignments
2. Gradually tighten the value type
3. Use the "create new object" pattern for modifications
4. Update dependent code to work with immutable data
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