mongodb/node-mongodb-native · error · MongoInvalidArgumentError
Cursor options must be an object
Error message
Cursor options must be an object
What it means
Thrown by AggregateOperation when options.cursor is defined but is not a plain object. The MongoDB aggregate command expects cursor to be a document (e.g. { batchSize: N }), and the driver validates the shape before sending the wire message.
Solutions
- Pass cursor as an object: aggregate(pipeline, { cursor: { batchSize: 100 } }).
- If you only need batchSize, prefer the top-level option aggregate(pipeline, { batchSize: 100 }) which the driver wraps for you.
- Drop the cursor option entirely if you want server defaults.
Example fix
// before
const cur = collection.aggregate(pipeline, { cursor: 100 });
// after
const cur = collection.aggregate(pipeline, { cursor: { batchSize: 100 } });
// or simply
const cur = collection.aggregate(pipeline, { batchSize: 100 }); Defensive patterns
Strategy: type-guard
Validate before calling
if (options?.cursor != null && (typeof options.cursor !== 'object' || Array.isArray(options.cursor))) {
throw new TypeError('aggregate cursor option must be a plain object');
}
await collection.aggregate(pipeline, options); Type guard
const isCursorOptions = (v: unknown): v is { batchSize?: number } =>
v != null && typeof v === 'object' && !Array.isArray(v); Prevention
- Prefer the top-level batchSize option; let the driver build the cursor object.
- Lint aggregate() calls for primitive cursor values.
When it happens
Trigger: Passing { cursor: 100 } or { cursor: true } or { cursor: 'batchSize' } to collection.aggregate() or db.aggregate(). Most commonly from mis-typed batchSize helpers that return a primitive.
Common situations: Wrapping batchSize incorrectly: aggregate(pipeline, { cursor: { batchSize } }) is right, but aggregate(pipeline, { cursor: batchSize }) is wrong. Plain-JS code that constructs options dynamically.
Related errors
- Argument "pipeline" must be an array of aggregation stages
- Cannot use $out or $merge stage with ITERATION timeoutMode
- A collection name must be determined before getMore
- A collection name must be determined before killCursors
- Argument "docs" must be an array of documents
AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11).
Data as JSON: /api/errors/fa114b5f86ed9744.
Report an issue: GitHub.
Appendix: source
Thrown at src/operations/aggregate.ts:84
// determine if we have a write stage, override read preference if so
this.hasWriteStage = false;
if (typeof options?.out === 'string') {
this.pipeline = this.pipeline.concat({ $out: options.out });
this.hasWriteStage = true;
} else if (pipeline.length > 0) {
const finalStage = pipeline[pipeline.length - 1];
if (finalStage.$out || finalStage.$merge) {
this.hasWriteStage = true;
}
}
if (!this.hasWriteStage) {
delete this.options.writeConcern;
}
if (options?.cursor != null && typeof options.cursor !== 'object') {
throw new MongoInvalidArgumentError('Cursor options must be an object');
}
this.SERVER_COMMAND_RESPONSE_TYPE = this.explain ? ExplainedCursorResponse : CursorResponse;
}
override get commandName() {
return 'aggregate' as const;
}
override get canRetryRead(): boolean {
return !this.hasWriteStage;
}
addToPipeline(stage: Document): void {
this.pipeline.push(stage);
}
override buildCommandDocument(): Document {View on GitHub (pinned to dce7939f86)