Automattic/mongoose · error · StrictModeError

Field ${fullPath} is immutable and strict = 'throw'

Error message

Field ${fullPath} is immutable and strict = 'throw'

What it means

This StrictModeError is thrown from update casting (castUpdate -> handleImmutable) when an update tries to modify a path declared immutable: true while strict is 'throw'. By default Mongoose silently strips immutable paths from updates; strict: 'throw' (schema-level or per-query) turns that stripping into this error. The options.overwriteImmutable: true query option bypasses the check.

Source

Thrown at lib/helpers/query/handleImmutable.js:38

    return false;
  }
  let immutable = schematype.options.immutable;

  if (typeof immutable === 'function') {
    immutable = immutable.call(ctx, ctx);
  }
  if (!immutable) {
    return false;
  }

  if (options?.overwriteImmutable) {
    return false;
  }
  if (strict === false) {
    return false;
  }
  if (strict === 'throw') {
    throw new StrictModeError(null,
      `Field ${fullPath} is immutable and strict = 'throw'`);
  }

  delete obj[key];
  return true;
};

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Remove the immutable path from the update payload (only update mutable fields)
  2. If the overwrite is intentional, pass { overwriteImmutable: true } as a query/bulkWrite option
  3. Keep strict as true (default) so immutable paths are silently stripped instead of throwing
  4. For upserts, set immutable initial values with $setOnInsert, which is exempt from immutable stripping

Example fix

// before
await Model.updateOne({ _id }, { $set: { createdAt: new Date() } }, { strict: 'throw' });

// after (intentional overwrite)
await Model.updateOne({ _id }, { $set: { createdAt: new Date() } }, { strict: 'throw', overwriteImmutable: true });
Defensive patterns

Strategy: validation

Validate before calling

// Remove immutable paths from an update before running it
function stripImmutable(schema, update) {
  for (const op of Object.keys(update)) {
    if (typeof update[op] !== 'object') continue;
    for (const p of Object.keys(update[op])) {
      const st = schema.path(p);
      if (st?.options?.immutable && op !== '$setOnInsert') delete update[op][p];
    }
  }
  return update;
}

Type guard

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

Try / catch

try {
  await Model.updateOne(f, u, { strict: 'throw' });
} catch (err) {
  if (err instanceof mongoose.Error.StrictModeError) {
    // strip the immutable field from the payload, or re-run with { overwriteImmutable: true }
  } else throw err;
}

Prevention

When it happens

Trigger: Schema has createdAt: { type: Date, immutable: true } and you run Model.updateOne({ _id }, { $set: { createdAt: new Date() } }, { strict: 'throw' }), or the schema/query is configured with strict: 'throw' and the update touches any immutable path. Also fires via bulkWrite updateOne unless its overwriteImmutable is set.

Common situations: Data-ingestion or sync jobs that rewrite whole documents including immutable createdAt/owner fields; generic CRUD handlers that $set every submitted field; enabling strict:'throw' globally to harden an app and surfacing previously-silent immutable stripping.

Related errors


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