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
- Register the missing discriminator on the base model: Base.discriminator('Click', ClickSchema) (with { value: 1 } for value-based discriminators) before create() runs
- Fix the discriminator key value in the input document to exactly match a registered discriminator name or value (lookup is case-sensitive)
- 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)
- 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
- Whitelist allowed discriminator values at the API boundary and reject unknown ones early
- Centralize discriminator registration in one module imported before any create/insertOne
- After renaming/removing a discriminator, grep data producers for the old __t value
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
- Aggregate `near()` must be called with non-nullish argument
- Invalid sort() argument. Must be a string or object.
- If thenExpr or elseExpr is string, it must be either $$DESCE
- Field `${path}` is not in schema and strict mode is set to t
- Path "${path}" is not in schema, strict mode is `true`, and
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/3d8ce328ffc59079.
Report an issue: GitHub.