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

  1. Provide the _id at creation: new User({ _id: new mongoose.Types.ObjectId() }) or the slug value for String _id
  2. Remove `_id: false` from top-level model schemas
  3. Give custom _id types a default: `_id: { type: String, default: () => generateId() }`
  4. 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

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


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