Automattic/mongoose · error · MongooseBulkWriteError

bulkWrite failed with

Error message

bulkWrite failed with ${validationErrors.length} Mongoose validation errors: ${preview}

What it means

Connection.prototype.bulkWrite() validates every op ({ model, name, document, ... }) against the model's schema before sending anything to MongoDB. With { ordered: false } it collects per-op failures (schema validation, unregistered model name, unrecognized op name) into validationErrors; normally invalid ops are skipped and reported on the result, but with options.throwOnValidationError: true Mongoose throws a MongooseBulkWriteError instead. This throw site (lib/connection.js:560) fires only when every op in the batch failed validation, so validOps is empty and nothing was sent to the server.

Solutions

  1. Catch the error and inspect err.validationErrors and err.results to see exactly which ops failed and why, then fix those documents
  2. Pre-validate each document before batching: const vErr = new conn.model(op.model)(op.document).validateSync()
  3. Drop throwOnValidationError and handle partial success via res.mongoose.validationErrors
  4. Relax or correct the schema rules if the strictness is unintended

Example fix

// before
await conn.bulkWrite(ops, { ordered: false, throwOnValidationError: true }); // throws when ALL ops invalid

// after: pre-validate, split good/bad ops
const good = [], bad = [];
for (const op of ops) {
  const Model = conn.model(op.model);
  const vErr = new Model(op.document ?? {}).validateSync();
  if (vErr) bad.push(op); else good.push(op);
}
if (good.length) await conn.bulkWrite(good, { ordered: false });
Defensive patterns

Strategy: try-catch

Validate before calling

for (const op of ops) {
  const Model = conn.model(op.model);
  const vErr = new Model(op.document ?? {}).validateSync();
  if (vErr) throw new Error(`op ${op.name} invalid: ${vErr.message}`);
}

Try / catch

try {
  const res = await conn.bulkWrite(ops, { ordered: false, throwOnValidationError: true });
} catch (err) {
  if (err?.name === 'MongooseBulkWriteError') {
    // nothing was written (validOps was empty)
    err.validationErrors.forEach(v => console.error(v.message));
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: await conn.bulkWrite([{ model: 'Test', name: 'insertOne', document: { /* missing required field */ } }], { ordered: false, throwOnValidationError: true }) where all documents violate schema rules (required, enum, match, custom validators), or every op references an unregistered model name or unrecognized op name.

Common situations: Bulk import / ETL jobs feeding unvalidated data; a schema tightened (new required field or enum) without cleaning historical inputs; opting into all-or-nothing semantics with throwOnValidationError.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at lib/connection.js:560

          )
        );
      } else {
        validOps.push({ ...op, namespace: Model.namespace() });
        validOpIndexes.push(i);
      }
    }

    if (asyncValidations.length > 0) {
      await Promise.all(asyncValidations);
    }

    validationErrors = validationErrors.
      sort((v1, v2) => v1.index - v2.index).
      map(v => v.error);

    if (validOps.length === 0) {
      if (options.throwOnValidationError && validationErrors.length) {
        throw new MongooseBulkWriteError(
          validationErrors,
          results,
          res,
          'bulkWrite'
        );
      }
      const BulkWriteResult = this.base.driver.get().BulkWriteResult;
      const res = new BulkWriteResult(getDefaultBulkwriteResult(), false);
      return decorateBulkWriteResult(res, validationErrors, results);
    }

    let error;
    [res, error] = await this.client.bulkWrite(validOps, options).
      then(res => ([res, null])).
      catch(err => ([null, err]));

    for (let i = 0; i < validOpIndexes.length; ++i) {
      results[validOpIndexes[i]] = null;

View on GitHub (pinned to 49cdab0136)