Automattic/mongoose · error · MongooseError

Discriminator "${doc[discriminatorKey]}" not found for model

Error message

Discriminator "${doc[discriminatorKey]}" not found for model "${this.modelName}"

What it means

When creating a single document (non-array form of Model.create()), Mongoose resolves the concrete model by looking up doc[discriminatorKey] (default '__t') in this.discriminators, falling back to getDiscriminatorByValue() for value-based discriminators. If the model has discriminators registered and the stored value matches neither a discriminator name nor a registered mapping value, Model resolves to null and this MongooseError is thrown.

Source

Thrown at lib/model.js:2756

    return Array.isArray(doc) ? [] : null;
  }
  let res = [];
  const immediateError = typeof options.aggregateErrors === 'boolean' ? !options.aggregateErrors : true;

  delete options.aggregateErrors; // dont pass on the option to "$save"

  if (options.session && !options.ordered && args.length > 1) {
    throw new MongooseError('Cannot call `create()` with a session and multiple documents unless `ordered: true` is set');
  }

  if (!Array.isArray(doc) && args.length === 1) {
    let toSave = doc;

    const Model = this.discriminators && doc[discriminatorKey] != null ?
      this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
      this;
    if (Model == null) {
      throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
      `found for model "${this.modelName}"`);
    }

    if (!(toSave instanceof Model)) {
      toSave = new Model(toSave);
    }

    await toSave.$save(options);

    return toSave;
  }

  if (options.ordered) {
    for (let i = 0; i < args.length; i++) {
      try {
        const doc = args[i];
        const Model = this.discriminators && doc[discriminatorKey] != null ?
          this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Register the missing discriminator on the base model: Base.discriminator('Click', ClickSchema) (with { value: 1 } for value-based discriminators) before create() runs
  2. Fix the discriminator key value in the input document to exactly match a registered discriminator name or value (lookup is case-sensitive)
  3. If the unknown key means 'use the base model', delete it before creating: delete doc.__t (a null/undefined key routes to this/base model)
  4. If stale __t values are expected from legacy data, strip or remap them in a pre-save hook or sanitize step before calling create()

Example fix

// before
Event.create({ __t: 'clic', kind: 'x' }); // throws: Discriminator "clic" not found

// after
Event.discriminator('click', ClickSchema);
await Event.create({ __t: 'click', kind: 'x' });
Defensive patterns

Strategy: validation

Validate before calling

function hasKnownDiscriminator(Model, doc) {
  const key = Model.schema.options.discriminatorKey; // default '__t'
  const value = doc?.[key];
  if (value == null || !Model.discriminators) return true; // routes to base model
  if (Model.discriminators[value] != null) return true; // by name
  return Object.values(Model.discriminators).some(D =>
    D.schema?.discriminatorMapping &&
    D.schema.discriminatorMapping.value === value // value-based
  );
}
// if (!hasKnownDiscriminator(Base, doc)) throw new Error(`unknown ${Base.modelName} discriminator`);

Try / catch

try {
  const doc = await Base.create(payload);
} catch (err) {
  if (err instanceof mongoose.MongooseError && /^Discriminator ".*" not found/.test(err.message)) {
    // bad discriminator key from upstream data: reject payload or fall back to base
    delete payload.__t;
    return Base.create(payload);
  }
  throw err;
}

Prevention

When it happens

Trigger: BaseModel.create({ __t: 'Typo', ... }) where 'Typo' was never registered via Base.discriminator('Typo', schema); value-based discriminators created without a matching { value } option; data deserialized from a queue/another service carrying an unknown __t.

Common situations: Typos or case mismatches in the discriminator key value ('click' vs 'Click'); renaming or removing a discriminator while old clients still send the old __t; value-based discriminators where a numeric/enum value was not registered; test fixtures referencing discriminators that were moved to another base.

Related errors


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