GrapesJS/grapesjs · error
Cannot modify immutable record
Error message
Cannot modify immutable record
What it means
DataRecord instances in the GrapesJS Data Sources layer can be marked immutable (`attributes.mutable === false`). Once a record is persisted/created (isNew() false) and immutable, `set()` refuses all mutations to protect records that other parts (components bound via data sources) depend on.
Source
Thrown at packages/core/src/data_sources/model/DataRecord.ts:153
*
* @param {String|Object} attributeName - The name of the attribute to set, or an object of key-value pairs.
* @param {any} [value] - The value to set for the attribute.
* @param {Object} [options] - Options to apply when setting the attribute.
* @param {Boolean} [options.avoidTransformers] - If true, transformers will not be applied.
* @returns {DataRecord} - The instance of the DataRecord.
* @name set
* @example
* record.set('name', 'newValue');
* // Sets 'name' property to 'newValue'
*/
set<A extends _StringKey<T>>(
attributeName: DeepPartialDot<T> | A,
value?: SetOptions | T[A] | undefined,
options?: SetOptions | undefined,
): this;
set(attributeName: unknown, value?: unknown, options?: SetOptions): DataRecord {
if (!this.isNew() && this.attributes.mutable === false) {
throw new Error('Cannot modify immutable record');
}
const onRecordSetValue = this.dataSource?.transformers?.onRecordSetValue;
const applySet = (key: string, val: unknown, opts: SetOptions = {}) => {
const newValue =
opts?.avoidTransformers || !onRecordSetValue
? val
: onRecordSetValue({
id: this.id,
key,
value: val,
});
super.set(key, newValue, opts);
// This ensures to trigger the change event with partial updates
super.set({ __p: opts.partial ? true : undefined } as any, opts);
};
View on GitHub (pinned to 2bdeda85b8)
Solutions
- Create a new record instead of mutating, or remove and re-add the record with new values.
- Set `mutable: true` in the record/schema definition when creating the data source if mutations are intended.
- Use `opts` on the collection/schema level that allows mutation, or reset the whole data source via `setRecords`/`setDataResolver` rather than setting individual fields.
Example fix
// before
record.set('title', 'new value'); // throws on immutable record
// after
datasource.removeRecord(record.id);
datasource.addRecord({ id: record.id, title: 'new value' }); Defensive patterns
Strategy: try-catch
Validate before calling
if (record && record.isNew && !record.isNew() && record.get('mutable') === false) {
// treat as read-only: skip set or recreate the record
} Type guard
function isMutableRecord(r) {
return !r || r.isNew() || r.attributes?.mutable !== false;
} Try / catch
try {
record.set('title', value);
} catch (err) {
if (err.message === 'Cannot modify immutable record') {
datasource.removeRecord(record.id, { dangerously: true });
datasource.addRecord({ ...record.attributes, title: value });
} else throw err;
} Prevention
- Check the `mutable` flag (or schema definition) before mutating records
- Design schemas with mutable:true if records need in-place updates
- Treat fetched records as immutable snapshots and write new records for changes
When it happens
Trigger: Calling `record.set(key, value)` (directly or via `dataResolver`/path updates like `resetDataSourcePath`/`setDataResolver`/`upSchema`) on an existing record whose `mutable` flag is false.
Common situations: Fetching records from a provider with schema defined as non-mutable, then trying to update a record value; mutating records loaded by `dataSource.load()`; API changes where records default to immutable after creation.
Related errors
- Cannot remove immutable record
- Target option is required
- await response.text()
- 'container' is required
- Parser code "${parserCode}" not found
AI-assisted analysis of GrapesJS/grapesjs@2bdeda85b8 (2026-08-30).
Data as JSON: /api/errors/72b1023d175a4653.
Report an issue: GitHub.