mongodb/node-mongodb-native · error · MongoAPIError

Client bulk write update models must only contain atomic mod

Error message

Client bulk write update models must only contain atomic modifiers (start with $) and must not be empty.

What it means

Thrown by validateUpdate() when an updateOne/updateMany model's update document is empty or its first key does not start with '$'. Per the cross-driver spec, update operations in client bulk writes MUST use atomic operators ($set, $inc, etc.); replacement-style documents are not allowed here (use replaceOne instead). Classified as MongoAPIError.

Source

Thrown at src/operations/client_bulk_write/command_builder.ts:373

 * @param model - The update many model.
 * @param index - The namespace index.
 * @returns the operation.
 */
export const buildUpdateManyOperation = (
  model: ClientUpdateManyModel<Document>,
  index: number,
  options: BSONSerializeOptions
): ClientUpdateOperation => {
  return createUpdateOperation(model, index, true, options);
};

/**
 * Validate the update document.
 * @param update - The update document.
 */
function validateUpdate(update: Document, options: BSONSerializeOptions) {
  if (!hasAtomicOperators(update, options)) {
    throw new MongoAPIError(
      'Client bulk write update models must only contain atomic modifiers (start with $) and must not be empty.'
    );
  }
}

/**
 * Creates a delete operation based on the parameters.
 */
function createUpdateOperation(
  model: ClientUpdateOneModel<Document> | ClientUpdateManyModel<Document>,
  index: number,
  multi: boolean,
  options: BSONSerializeOptions
): ClientUpdateOperation {
  // Update documents provided in UpdateOne and UpdateMany write models are
  // required only to contain atomic modifiers (i.e. keys that start with "$").
  // Drivers MUST throw an error if an update document is empty or if the
  // document's first key does not start with "$".

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Wrap the fields in $set: update: { $set: { field: value } }.
  2. Use a replaceOne model if you intend a full-document replacement.
  3. Validate before sending: assert model.update && Object.keys(model.update)[0]?.startsWith('$').

Example fix

// before
await client.bulkWrite([{
  namespace: 'db.coll',
  name: 'updateOne',
  filter: { _id: 1 },
  update: { status: 'done' } // no $ operator
}]);

// after
await client.bulkWrite([{
  namespace: 'db.coll',
  name: 'updateOne',
  filter: { _id: 1 },
  update: { $set: { status: 'done' } }
}]);
Defensive patterns

Strategy: validation

Validate before calling

function isValidUpdate(update) {
  const keys = Object.keys(update);
  return keys.length > 0 && keys[0].startsWith('$');
}

Type guard

function isAtomicUpdate(v): boolean {
  return v != null && typeof v === 'object' && Object.keys(v).every(k => k.startsWith('$'));
}

Prevention

When it happens

Trigger: Passing client.bulkWrite() an updateOne/updateMany model whose update is { field: value } (no $ operator) or {} (empty). Accidentally using a replacement document where an atomic update was intended.

Common situations: Treating updateOne like replaceOne; building update dynamically and ending with an empty object; copy-paste from collection.updateOne that accepted full replacements in older patterns.

Related errors


AI-assisted analysis of mongodb/node-mongodb-native@3366c21a63 (2026-08-04). Data as JSON: /data/errors/c7b4d995bb4e67ea.json. Report an issue: GitHub.