immerjs/immer · error · Error
[Immer] ${msg}
Error message
[Immer] ${msg} What it means
This is the single development-mode throw site of Immer's die() function (src/utils/errors.ts:41-45). die(code, ...args) indexes the errors table, formats the entry (literal string or function), and throws new Error("[Immer] " + msg). Every internal die(n) call funnels here: codes 0-15 are defined in src/utils/errors.ts:3-38, and codes 16-19 are appended by the patches plugin via errorOffset 16 (src/plugins/patches.ts:36-48). It exists only when process.env.NODE_ENV !== "production", giving developers the full human-readable message.
Source
Thrown at src/utils/errors.ts:45
return `'current' expects a draft, got: ${thing}`
},
"Object.defineProperty() cannot be used on an Immer draft",
"Object.setPrototypeOf() cannot be used on an Immer draft",
"Immer only supports deleting array indices",
"Immer only supports setting array indices and the 'length' property",
function (thing: string) {
return `'original' expects a draft, got: ${thing}`
}
// Note: if more errors are added, the errorOffset in Patches.ts should be increased
// See Patches.ts for additional errors
]
: []
export function die(error: number, ...args: any[]): never {
if (process.env.NODE_ENV !== "production") {
const e = errors[error]
const msg = isFunction(e) ? e.apply(null, args as any) : e
throw new Error(`[Immer] ${msg}`)
}
throw new Error(
`[Immer] minified error nr: ${error}. Full error at: https://bit.ly/3cXEKWf`
)
}
View on GitHub (pinned to d2c158f5ba)
Solutions
- Read the full [Immer] message - it names the exact problem; cross-reference the code index with the errors table in src/utils/errors.ts:3-38 (and the patches block at src/plugins/patches.ts:36-48 for codes >= 16).
- Fix the specific misuse the message describes (return a new value OR mutate the draft, not both; mark custom classes with [immerable]: true; pass a draft from produce/createDraft to current()/original()).
- Add the matching runtime check before the call (isDraftable, isDraft, isMap/isSet) so the bad input never reaches Immer.
- If the message references a missing plugin (code 0), call enableXY() once at application init before first use.
Example fix
// before: produce called on a non-draftable primitive -> die(1)
produce(123, draft => {
draft.value = 1
})
// after: produce on a draftable plain object
produce({n: 1}, draft => {
draft.n = 2
}) Defensive patterns
Strategy: validation
Validate before calling
// Validate the most common preconditions before calling Immer APIs.
import {isDraftable, isDraft, produce} from "immer"
function safeProduce<S extends unknown>(
base: S,
recipe: (draft: S) => void | S
) {
if (!isDraftable(base)) {
throw new TypeError(`Expected a draftable base, got ${typeof base}`)
}
return produce(base, recipe)
}
// guard current()/original() callers explicitly
function safeOriginal<T>(d: T): T {
if (!isDraft(d)) throw new TypeError("original() expects a draft")
return (d as any)[Symbol.for("immer-state")].base_ as T
} Type guard
import {isDraftable, isDraft} from "immer"
const isSafeImmerInput = (v: unknown): v is object =>
isDraftable(v)
const isImmerDraft = <T>(v: T): v is T =>
isDraft(v) Try / catch
try {
const next = produce(state, draft => {
/* recipe */
})
} catch (err) {
if (err instanceof Error && err.message.startsWith("[Immer]")) {
// dev message names the exact misuse and code index
console.error("Immer error:", err.message)
}
throw err
} Prevention
- Call isDraftable() on the base before produce() to catch non-draftable inputs (plain objects, arrays, Map, Set, or [immerable] classes only).
- Never both return a new value and mutate the draft in one producer - pick one path.
- Call isDraft() before current()/original() to avoid codes 10/15.
- Enable every plugin you use (enablePatches, enableMapSet) once at app init to avoid code 0.
- Keep producers pure and synchronous; do not leak drafts into async callbacks (code 3, revoked proxy).
When it happens
Trigger: Any misuse of an Immer API: calling produce on a non-draftable value (code 1); returning a new value while also mutating the draft (code 4); a circular reference (code 5); current()/original() on a non-draft (codes 10/15); using a feature whose plugin was not enabled, e.g. patches without enablePatches() (code 0); Object.defineProperty/setPrototypeOf on a draft (11/12); deleting a non-index object property / setting non-index array keys (13/14); or applyPatches hitting an unresolvable path, reserved prop, or unsupported op (codes 18/19/17).
Common situations: Local dev or test runs (NODE_ENV unset or "development"); a value slipping past TypeScript types, such as a class instance not marked [immerable]: true; a producer that both returns a new object and mutates the draft; passing an already-finalized, frozen, or revoked proxy; mixing Map/Set patch shapes; calling finishDraft on something createDraft did not produce.
Related errors
AI-assisted analysis of immerjs/immer@d2c158f5ba (2026-08-13).
Data as JSON: /api/errors/05eb0b35af9f2b6b.
Report an issue: GitHub.