Automattic/mongoose · error · StrictModeError

Path `${path}` is immutable and strict mode is set to throw.

Error message

Path `${path}` is immutable and strict mode is set to throw.

What it means

Document-level counterpart of the update-side immutable guard: assigning to a path declared immutable: true on a persisted (non-new) document is normally silently ignored (the previous value is kept). If the document's strict mode is 'throw' and the new value differs from the stored prior value, the immutable setter throws this StrictModeError instead. Immutable paths are write-once: set at creation, unchangeable on loaded documents.

Source

Thrown at lib/helpers/schematype/handleImmutable.js:44

    if (this.isNew) {
      return v;
    }
    if (options?.overwriteImmutable) {
      return v;
    }

    const _immutable = typeof immutable === 'function' ?
      immutable.call(this, this) :
      immutable;
    if (!_immutable) {
      return v;
    }

    const _value = this.$__.priorDoc != null ?
      this.$__.priorDoc.$__getValue(path) :
      this.$__getValue(path);
    if (this.$__.strictMode === 'throw' && v !== _value) {
      throw new StrictModeError(path, 'Path `' + path + '` is immutable ' +
        'and strict mode is set to throw.', true);
    }

    return _value;
  };
}

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Skip assignment to immutable paths when updating existing documents (check doc.isNew or the schema path's immutable option)
  2. If the field must be editable, remove immutable: true or make it conditional with a function (immutable: function() { ... })
  3. Force an intentional change through the query layer: Model.updateOne({ _id: doc._id }, { $set: { name: 'new' } }, { overwriteImmutable: true })
  4. Re-create the document instead of mutating an immutable field on a loaded one

Example fix

// before
const user = await User.findById(id);
user.email = req.body.email; // email is immutable, strict: 'throw' -> throws
await user.save();

// after
const user = await User.findById(id);
user.displayName = req.body.displayName; // mutable
await user.save();
Defensive patterns

Strategy: validation

Validate before calling

// Skip assignment of immutable paths on loaded documents
function assignMutable(doc, patch) {
  const schema = doc.schema;
  for (const [k, v] of Object.entries(patch)) {
    if (!doc.isNew && schema.path(k)?.options?.immutable) continue;
    doc.set(k, v);
  }
}

Type guard

const isImmutablePath = (schema, path) => Boolean(schema.path(path)?.options?.immutable);

Try / catch

try {
  doc.set('name', value);
  await doc.save();
} catch (err) {
  if (err instanceof mongoose.Error.StrictModeError && /immutable/.test(err.message)) {
    // reject the edit or route through Model.updateOne(..., { overwriteImmutable: true })
  } else throw err;
}

Prevention

When it happens

Trigger: doc.name = 'new' or doc.set('name', 'new') where the schema declares name: { type: String, immutable: true }, the document was loaded from the DB (isNew false, priorDoc exists), and the schema (or document) uses strict: 'throw'.

Common situations: Generic form handlers that assign every submitted field onto a loaded document; schemas hardened with strict:'throw'; write-once fields like username, email, or tenant id edited after onboarding; bulk import scripts mutating loaded docs.

Related errors


AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21). Data as JSON: /api/errors/9353aa690e868950. Report an issue: GitHub.