Automattic/mongoose · error · MongooseBulkWriteError

bulkWrite failed with ${validationErrors.length} Mongoose va

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.

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)

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.