mongodb/node-mongodb-native · error · MongoInvalidArgumentError
Could not serialize operation to BSON
Error message
Could not serialize operation to BSON
What it means
Thrown when BSON.serialize fails on a single client-side bulk write operation. The driver wraps the underlying BSON error (cause) so you can see exactly why serialization aborted. Common BSON-serialization failures include values that BSON cannot represent: undefined nested in documents (with the driver's checkKeys behavior), functions, Symbols, circular references, or oversized keys.
Solutions
- Inspect error.cause — it carries the exact BSON error message and path.
- Strip non-serializable fields (functions, symbols, undefined) before building the model; convert class instances to plain objects.
- If circular references are involved, serialize with a replacer or use EJSON for the problematic values.
- For BigInt values, wrap with bson.Long or bson.Int32 as appropriate.
Example fix
// before
await client.bulkWrite([{
insertOne: { namespace: 'db.coll', document: { req, handler } } // req is a circular http request object
}]);
// after
await client.bulkWrite([{
insertOne: { namespace: 'db.coll', document: { id: req.id, url: req.url } }
}]); Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-validate with the same BSON the driver uses
import { serialize } from 'bson';
try {
serialize(opDocument, { checkKeys: false });
} catch (e) {
throw new Error(`Document will not serialize: ${e.message}`);
} Type guard
const isPlainSerializable = (v: unknown): boolean => {
if (v == null || typeof v !== 'object') return typeof v !== 'function' && typeof v !== 'symbol';
return Object.values(v).every(isPlainSerializable);
}; Try / catch
try {
await client.bulkWrite(models);
} catch (e) {
if (/Could not serialize operation to BSON/.test(e.message)) {
// inspect e.cause for the exact field, fix, and retry that batch
}
} Prevention
- Convert class instances to plain objects before inserting.
- Avoid putting request/response objects with circular refs into documents.
When it happens
Trigger: Passing a model whose filter/update/document contains a function, Symbol, circular reference, or a key containing '.' or starting with '$' where disallowed. Also a 32-bit-int overflow or a BigInt outside the supported range.
Common situations: Storing class instances with method references; serializing request objects from HTTP libraries that contain circular refs; accidentally including a Mongoose document with non-plain-object internals; passing Decimal128/Long constructed incorrectly.
Related errors
- Client bulk write operation
- ClientSession cannot be serialized to BSON.
- Could not serialize ns info to BSON
- Argument "operations" must be an array of documents
- Attempt to access memory outside buffer bounds: buffer…
AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11).
Data as JSON: /api/errors/d86a8998dd381917.
Report an issue: GitHub.
Appendix: source
Thrown at src/operations/client_bulk_write/command_builder.ts:136
while (this.currentModelIndex < this.models.length) {
const model = this.models[this.currentModelIndex];
const ns = model.namespace;
const nsIndex = namespaces.get(ns);
// Multi updates are not retryable.
if (model.name === 'deleteMany' || model.name === 'updateMany') {
this.isBatchRetryable = false;
}
if (nsIndex != null) {
// Build the operation and serialize it to get the bytes buffer.
const operation = buildOperation(model, nsIndex, this.pkFactory, this.options);
let operationBuffer;
try {
operationBuffer = BSON.serialize(operation);
} catch (cause) {
throw new MongoInvalidArgumentError(`Could not serialize operation to BSON`, { cause });
}
validateBufferSize('ops', operationBuffer, maxBsonObjectSize);
// Check if the operation buffer can fit in the command. If it can,
// then add the operation to the document sequence and increment the
// current length as long as the ops don't exceed the maxWriteBatchSize.
if (
commandLength + operationBuffer.length < maxMessageSizeBytes &&
command.ops.documents.length < maxWriteBatchSize
) {
// Pushing to the ops document sequence returns the total byte length of the document sequence.
commandLength = MESSAGE_OVERHEAD_BYTES + command.ops.push(operation, operationBuffer);
// Increment the builder's current model index.
this.currentModelIndex++;
} else {
// The operation cannot fit in the current command and will need to
// go in the next batch. Exit the loop.View on GitHub (pinned to dce7939f86)