How to fix Cannot use 'readonly' modifier in mapped type without keyof in TypeScript
This TypeScript error occurs when trying to use the readonly modifier in a mapped type without properly using the keyof operator. Mapped types require keyof to iterate over object keys when applying modifiers like readonly. The fix involves correctly structuring mapped types with keyof.
Quick fix
Fixes Cannot use 'readonly' modifier in mapped type without keyof
// ❌ Incorrect - causes the error
type ReadonlyObject = {
readonly [K in string]: any;
};
// ✅ Correct - uses keyof with a source type
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
// Example usage
interface User {
name: string;
age: number;
}
type ReadonlyUser = Readonly<User>;
// Equivalent to: { readonly name: string; readonly age: number; }- Understand the correct mapped type syntax for readonly
- Fix existing incorrect mapped type definitions
- Use built-in Readonly utility type
- Create custom readonly mapped types for specific use cases
// ❌ Incorrect - causes the errortype ReadonlyObject = {readonly [K in string]: any;};// ✅ Correct - uses keyof with a source typetype Readonly<T> = {readonly [K in keyof T]: T[K];};// Example usageinterface User {name: string;age: number;}type ReadonlyUser = Readonly<User>;// Equivalent to: { readonly name: string; readonly age: number; }Cannot use 'readonly' modifier in mapped type without keyof
What this error means
The error "Cannot use 'readonly' modifier in mapped type without keyof" occurs in TypeScript when you attempt to create a mapped type that applies the readonly modifier without properly using the keyof type operator. Mapped types in TypeScript allow you to create new types by transforming properties of existing types. When you want to make all properties of an object type readonly, you need to use a mapped type that iterates over the keys of the original type using keyof. The error happens because TypeScript expects mapped types that apply property modifiers (like readonly or ? for optional) to explicitly iterate over keys using the keyof operator. This ensures type safety and clarity about which properties are being transformed.
Typical symptoms
- TypeScript compiler shows error: "Cannot use 'readonly' modifier in mapped type without keyof"
- Code fails to compile when trying to create readonly versions of object types
- Mapped type definitions with readonly modifier cause compilation errors
- Attempts to make object properties immutable fail at compile time
Common causes
- Using readonly modifier directly in a mapped type without keyof iteration
- Incorrect mapped type syntax when trying to make properties readonly
- Missing keyof operator in mapped type definition
- Confusion between mapped types and regular type aliases
How to fix "Cannot use 'readonly' modifier in mapped type without keyof"
The correct syntax for creating a readonly version of an object type uses keyof to iterate over the keys:
// ❌ Incorrect - causes the error
type ReadonlyObject = {
readonly [K in string]: any;
};
// ✅ Correct - uses keyof with a source type
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
// Example usage
interface User {
name: string;
age: number;
}
type ReadonlyUser = Readonly<User>;
// Equivalent to: { readonly name: string; readonly age: number; }If you have code causing this error, update it to use the correct syntax:
// Before - causes error
type MyReadonlyType = {
readonly [K in 'prop1' | 'prop2']: string;
};
// After - fixed with proper mapped type
type Keys = 'prop1' | 'prop2';
type MyReadonlyType = {
readonly [K in Keys]: string;
};
// Or using a helper type
type MakeReadonly<T extends Record<string, any>> = {
readonly [K in keyof T]: T[K];
};TypeScript provides a built-in Readonly<T> utility type that handles this correctly:
interface Config {
apiUrl: string;
timeout: number;
retries: number;
}
// Use the built-in Readonly utility type
type ReadonlyConfig = Readonly<Config>;
// Equivalent to: {
// readonly apiUrl: string;
// readonly timeout: number;
// readonly retries: number;
// }
// You can also create partial readonly types
type PartiallyReadonly<T, K extends keyof T> = Readonly<Pick<T, K>> & Omit<T, K>;
type ConfigWithReadonlyApi = PartiallyReadonly<Config, 'apiUrl'>;
// apiUrl is readonly, timeout and retries are mutableFor advanced use cases, create custom mapped types with readonly:
// Deep readonly - makes nested objects readonly too
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
// Readonly except for specific keys
type ReadonlyExcept<T, K extends keyof T> = {
readonly [P in Exclude<keyof T, K>]: T[P];
} & {
[P in K]: T[P];
};
interface State {
count: number;
user: { name: string; email: string };
timestamp: Date;
}
type ReadonlyState = DeepReadonly<State>;
// All properties including nested 'user' object are readonly
type StateWithMutableCount = ReadonlyExcept<State, 'count'>;
// Only 'count' is mutable, others are readonlyVerify that your readonly types prevent mutation:
interface Product {
id: string;
name: string;
price: number;
details: {
category: string;
inStock: boolean;
};
}
type ReadonlyProduct = Readonly<Product>;
const product: ReadonlyProduct = {
id: '123',
name: 'Widget',
price: 29.99,
details: { category: 'Tools', inStock: true }
};
// These will cause TypeScript errors:
// product.name = 'New Name'; // Error: Cannot assign to 'name' because it is a read-only property
// product.details.category = 'New Category'; // Error with shallow Readonly
// product.details = { category: 'New', inStock: false }; // Error
// For deep readonly:
type DeepReadonlyProduct = DeepReadonly<Product>;
const deepProduct: DeepReadonlyProduct = { ...product };
// This will also error:
// deepProduct.details.category = 'New Category'; // Error with DeepReadonlyAdvanced notes
## Understanding Mapped Type Modifiers
TypeScript mapped types support two kinds of modifiers:
1. Readonly modifier (readonly): Makes properties immutable
2. Optionality modifier (?): Makes properties optional
You can also remove modifiers using -readonly and -?:
// Remove readonly from a type
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
// Remove optional modifier
type Required<T> = {
[K in keyof T]-?: T[K];
};## Common Pitfalls
1. Missing keyof: Always use keyof T when applying modifiers in mapped types
2. Shallow vs Deep: Built-in Readonly<T> only makes top-level properties readonly
3. Performance: Complex mapped types with many transformations can slow down type checking
4. Circular references: Avoid creating mapped types that reference themselves
## Real-World Use Cases
- Immutable state in Redux or state management libraries
- Configuration objects that shouldn't be modified after initialization
- API response types where data should be treated as immutable
- Library APIs that return readonly data structures
Frequently asked questions
- What does "Cannot use 'readonly' modifier in mapped type without keyof" mean?
- The error "Cannot use 'readonly' modifier in mapped type without keyof" occurs in TypeScript when you attempt to create a mapped type that applies the readonly modifier without properly using the keyof type operator. Mapped types in TypeScript allow you to create new types by transforming properties of existing types. When you want to make all properties of an object type readonly, you need to use a mapped type that iterates over the keys of the original type using keyof.
- How do I fix "Cannot use 'readonly' modifier in mapped type without keyof"?
- This TypeScript error occurs when trying to use the readonly modifier in a mapped type without properly using the keyof operator. Mapped types require keyof to iterate over object keys when applying modifiers like readonly. The fix involves correctly structuring mapped types with keyof.
- What causes "Cannot use 'readonly' modifier in mapped type without keyof"?
- Using readonly modifier directly in a mapped type without keyof iteration Incorrect mapped type syntax when trying to make properties readonly Missing keyof operator in mapped type definition
- Is the "Cannot use 'readonly' modifier in mapped type without keyof" error serious?
- It is a low-severity error — more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- 📚TypeScript Official Documentation
- 📖Typescriptlang.org - everyday-types.html(Official Docs)
- 📖Typescriptlang.org - mapped-types.html(Official Docs)
- 📖Typescriptlang.org - utility-types.html#readonlytype(Official Docs)
- 🐙Github.com - 24509(GitHub)
- 💬Stackoverflow.com - deepreadonly-object-typescript(Stack Overflow)