Skip to main content

typescript/prefer-readonly-record

Require Readonly<Record<K, V>> instead of mutable Record<K, V>.

Targeted pattern scope

This rule targets Record type references in TypeScript type positions.

What this rule reports

This rule reports Record<...> references that are not already wrapped by Readonly<...>.

Why this rule exists

Record<K, V> is writable by default. Wrapping it with Readonly<...> makes immutability explicit in API and model types.

❌ Incorrect

type Store = Record<string, number>;

type Nested = Promise<Record<string, Record<string, number>>>;

✅ Correct

type Store = Readonly<Record<string, number>>;

type Nested = Promise<
Readonly<Record<string, Readonly<Record<string, number>>>>
>;

Behavior and migration notes

This rule is autofixable. The fixer wraps each reported Record<...> with Readonly<...>.

Additional examples

type Flags = Record<string, boolean>;
// ❌ reported

type FlagsView = Readonly<Record<string, boolean>>;
// ✅ valid

ESLint flat config example

import etcMisc from "eslint-plugin-etc-misc";

export default [
{
plugins: { "etc-misc": etcMisc },
rules: {
"etc-misc/typescript/prefer-readonly-record": "error",
},
},
];

When not to use it

Disable this rule if mutable Record types are a deliberate project convention.

Package documentation

Rule catalog ID: R112

Further reading

Adoption resources

  • Start at warning level in CI, then move to error after cleanup.
  • Use focused codemods/autofix batches per package or directory.