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

  1. Create a new record instead of mutating, or remove and re-add the record with new values.
  2. Set `mutable: true` in the record/schema definition when creating the data source if mutations are intended.
  3. 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

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


AI-assisted analysis of GrapesJS/grapesjs@2bdeda85b8 (2026-08-30). Data as JSON: /api/errors/72b1023d175a4653. Report an issue: GitHub.