Automattic/mongoose · error · StrictModeError

Field `${prefix}${key}` is not in schema and strict mode is

Error message

Field `${prefix}${key}` is not in schema and strict mode is set to throw.

What it means

The leaf-value counterpart of the nested StrictModeError: when a (possibly dotted) update key resolves to no schematype and pathDetails.pathType is neither 'real' nor 'nested', strict mode says to skip the key; if the effective strict value is 'throw' Mongoose throws StrictModeError(prefix + key) instead of deleting it. Virtuals are explicitly exempted so updates that try to set a virtual are silently stripped rather than throwing (gh-6731).

Source

Thrown at lib/helpers/query/castUpdate.js:443

          schematype = _res.schematype;
          pathDetails = _res.type;
        }
      }

      let isStrict = strict;
      if (pathDetails?.schema && strict == null) {
        isStrict = pathDetails.schema.options.strict;
      }

      const skip = isStrict &&
        !schematype &&
        !/real|nested/.test(pathDetails.pathType);

      if (skip) {
        // Even if strict is `throw`, avoid throwing an error because of
        // virtuals because of #6731
        if (isStrict === 'throw' && schema.virtuals[checkPath] == null) {
          throw new StrictModeError(prefix + key);
        } else {
          delete obj[key];
        }
      } else {
        if (op === '$rename') {
          if (obj[key] == null) {
            throw new CastError('String', obj[key], `${prefix}${key}.$rename`);
          }
          const schematype = new SchemaString(`${prefix}${key}.$rename`, null, null, schema);
          obj[key] = schematype.castForQuery(null, obj[key], context);
          continue;
        }

        try {
          if (prefix.length === 0 || key.indexOf('.') === -1) {
            obj[key] = castUpdateVal(schematype, val, op, key, context, prefix + key);
          } else if (isStrict !== false || schematype != null) {
            // Setting a nested dotted path that's in the schema. We don't allow paths with '.' in

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Fix the field name to match the schema path
  2. Add the field to the schema (Mixed if free-form)
  3. Allow schemaless writes intentionally with Model.updateOne(f, upd, { strict: false }) or schema strict: false

Example fix

// before: 'nmae' typo, schema strict: 'throw'
User.updateOne({ _id }, { $set: { nmae: 'Alice' } });

// after
User.updateOne({ _id }, { $set: { name: 'Alice' } });
Defensive patterns

Strategy: validation

Validate before calling

// fail fast on unknown top-level update keys under strict:'throw'
function assertUpdateKeys(update, schema) {
  for (const [op, fields] of Object.entries(update)) {
    if (!op.startsWith('$')) continue;
    for (const key of Object.keys(fields || {})) {
      if (schema.path(key) == null && schema.virtuals[key] == null) {
        throw new Error(`unknown update key ${key} under ${op}`);
      }
    }
  }
}

Try / catch

try { await Model.updateOne(f, u); } catch (err) { if (err instanceof mongoose.Error.StrictModeError) { /* message names the bad path — fix typo or add to schema; virtuals are exempt */ } throw err; }

Prevention

When it happens

Trigger: Schema with strict: 'throw' plus Model.updateOne(f, { $set: { nmae: 'A' } }) (typo path); setting a non-virtual, non-schema field to a scalar value; per-query strict: 'throw' via Model.updateOne(f, upd, { strict: 'throw' }).

Common situations: Typos in field names that strict: true would silently drop; fields added directly in MongoDB by other services but absent from the Mongoose schema; enabling strict: 'throw' to catch such mistakes and then hitting legacy updates.

Related errors


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