Automattic/mongoose · error · MongooseError
document must have an _id before saving
Error message
document must have an _id before saving
What it means
When saving a new document, Mongoose must know its _id so later updates target the right document; if the object sent to insertOne has no _id after serialization, save() throws this MongooseError. It happens when the schema disables or cannot auto-generate _id: `_id: false`, a custom _id type with no default (e.g. String) and no value supplied, or _id explicitly overwritten with undefined.
Source
Thrown at lib/model.js:427
let result = null;
let where = null;
try {
const saveOptions = _createSaveOptions(this, options);
if (this.$isNew) {
const hasOnlyPrimitiveValues = this.$__hasOnlyPrimitiveValues();
// send entire doc
const obj = hasOnlyPrimitiveValues ?
this.$__toObjectShallow() :
this.toObject(saveToObjectOptions);
if ((obj || {})._id === void 0) {
// documents must have an _id else mongoose won't know
// what to update later if more changes are made. the user
// wouldn't know what _id was generated by mongodb either
// nor would the ObjectId generated by mongodb necessarily
// match the schema definition.
throw new MongooseError('document must have an _id before saving');
}
this.$__version(true, obj);
this.$__reset();
_setIsNew(this, false, hasOnlyPrimitiveValues);
// Make it possible to retry the insert
this.$__.inserting = true;
result = await this[modelCollectionSymbol].insertOne(obj, saveOptions).catch(err => {
_setIsNew(this, true, hasOnlyPrimitiveValues);
throw err;
});
} else {
// Make sure we don't treat it as a new object on error,
// since it already exists
this.$__.inserting = false;
const pathsToSave = Array.isArray(options.pathsToSave) ? options.pathsToSave : null;
const pathsToSaveSet = pathsToSave != null ? new Set(pathsToSave) : null;
const delta = this.$__delta(pathsToSave, pathsToSaveSet);View on GitHub (pinned to 49cdab0136)
Solutions
- Provide the _id at creation: new User({ _id: new mongoose.Types.ObjectId() }) or the slug value for String _id
- Remove `_id: false` from top-level model schemas
- Give custom _id types a default: `_id: { type: String, default: () => generateId() }`
- Never let spreads/merges overwrite _id with undefined before save
Example fix
// before
const UserSchema = new Schema({ _id: false, name: String });
const User = mongoose.model('User', UserSchema);
await User.create({ name: 'x' }); // throws
// after
const UserSchema = new Schema({ _id: { type: String, default: () => nanoid() }, name: String });
await User.create({ name: 'x' }); Defensive patterns
Strategy: validation
Validate before calling
// Ensure an _id exists before saving a new document
function withId(doc) {
if (doc.isNew && doc._id == null) {
doc._id = new mongoose.Types.ObjectId();
}
return doc;
}
await withId(doc).save(); Type guard
const hasId = (doc) => doc?._id !== undefined && doc?._id !== null;
Try / catch
try {
await doc.save();
} catch (err) {
if (err instanceof mongoose.MongooseError && /must have an _id/.test(err.message)) {
// generate the id (new mongoose.Types.ObjectId() or app-level id) and retry once
} else throw err;
} Prevention
- Do not set _id: false on top-level model schemas
- Give custom _id types a default generator or require the value at creation
- Never assign undefined to _id (watch spreads/merges of user payloads onto documents)
When it happens
Trigger: new Schema({ _id: false, ... }) used for a top-level model then new Model({}).save(); new Schema({ _id: String }) saved without providing the id; doc._id = undefined (or a spread/merge overwriting _id with undefined) before save.
Common situations: Subdocument-style schemas (where _id: false is common) copied for top-level models; migrations from SQL expecting DB-side id generation; human-readable slug ids (_id: String) where some creation paths omit the slug.
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
- Aggregate.prototype.exec() no longer accepts a callback
- Field `${path}` is not in schema and strict mode is set to t
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/b333c0f41e72d2f9.
Report an issue: GitHub.