Automattic/mongoose · error · StrictModeError

Path "${path}" is not in schema, strict mode is `true`, and

Error message

Path "${path}" is not in schema, strict mode is `true`, and upsert is `true`.

What it means

With strict true (the default) and upsert true, mongoose treats schema-unknown paths as fatal rather than stripping them: cast() checks the upsert+strict combination first and throws StrictModeError with this message. A non-upsert update would silently delete the unknown path; the upsert makes it an error.

Source

Thrown at lib/cast.js:301

              }
            }

            _cast(value, numbertype, context);
            continue;
          }
        }

        if (schema.nested[path]) {
          continue;
        }

        const strict = 'strict' in options ? options.strict : schema.options.strict;
        const strictQuery = getStrictQuery(options, schema._userProvidedOptions, schema.options, context);
        if (options.upsert && strict) {
          if (strict === 'throw') {
            throw new StrictModeError(path);
          }
          throw new StrictModeError(path, 'Path "' + path + '" is not in ' +
            'schema, strict mode is `true`, and upsert is `true`.');
        } if (strictQuery === 'throw') {
          throw new StrictModeError(path, 'Path "' + path + '" is not in ' +
            'schema and strictQuery is \'throw\'.');
        } else if (strictQuery) {
          delete obj[path];
        }
      } else if (val == null) {
        continue;
      } else if (utils.isPOJO(val)) {
        any$conditionals = Object.keys(val).some(isOperator);

        if (!any$conditionals) {
          obj[path] = schematype.castForQuery(
            null,
            val,
            context
          );

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Add the field to the schema
  2. Strip unknown keys from the update before the call (pick only modeled fields)
  3. Opt out per call: Model.updateOne(filter, update, { upsert: true, strict: false })
  4. As a last resort set { strict: false } on the schema -- note this disables the guard for all writes

Example fix

// before
await Model.updateOne({ name: 'x' }, { name: 'x', extra: 1 }, { upsert: true }); // `extra` not in schema

// after -- declare `extra` in the schema, or allow it for this call only:
await Model.updateOne({ name: 'x' }, { name: 'x', extra: 1 }, { upsert: true, strict: false });
Defensive patterns

Strategy: validation

Validate before calling

function pickKnownPaths(Model, doc) {
  const out = {};
  for (const k of Object.keys(doc)) {
    if (k in Model.schema.paths || k.startsWith('$')) out[k] = doc[k];
  }
  return out;
}
await Model.updateOne(filter, pickKnownPaths(Model, update), { upsert: true });

Try / catch

try {
  await Model.updateOne(filter, update, { upsert: true });
} catch (err) {
  if (err.name === 'StrictModeError' && err.message.includes('upsert')) {
    // either declare the path in the schema, strip it, or set strict: false for this call
  }
  throw err;
}

Prevention

When it happens

Trigger: Model.updateOne(filter, { unknownField: 1 }, { upsert: true }) on a default-strict schema; findOneAndUpdate(..., { upsert: true }) whose update contains client-added fields; $setOnInsert keys that are not modeled.

Common situations: Newer clients sending new fields before the schema is updated; payloads carrying metadata keys not modeled; sharing one object between save() and upsert code paths; renaming schema fields while old writers keep persisting the old name.

Related errors


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