{"record":{"id":"fa114b5f86ed9744","repo":"mongodb/node-mongodb-native","slug":"cursor-options-must-be-an-object","errorCode":null,"errorMessage":"Cursor options must be an object","messagePattern":"Cursor options must be an object","errorType":"exception","errorClass":"MongoInvalidArgumentError","httpStatus":null,"severity":"error","filePath":"src/operations/aggregate.ts","lineNumber":84,"sourceCode":"\n    // determine if we have a write stage, override read preference if so\n    this.hasWriteStage = false;\n    if (typeof options?.out === 'string') {\n      this.pipeline = this.pipeline.concat({ $out: options.out });\n      this.hasWriteStage = true;\n    } else if (pipeline.length > 0) {\n      const finalStage = pipeline[pipeline.length - 1];\n      if (finalStage.$out || finalStage.$merge) {\n        this.hasWriteStage = true;\n      }\n    }\n\n    if (!this.hasWriteStage) {\n      delete this.options.writeConcern;\n    }\n\n    if (options?.cursor != null && typeof options.cursor !== 'object') {\n      throw new MongoInvalidArgumentError('Cursor options must be an object');\n    }\n\n    this.SERVER_COMMAND_RESPONSE_TYPE = this.explain ? ExplainedCursorResponse : CursorResponse;\n  }\n\n  override get commandName() {\n    return 'aggregate' as const;\n  }\n\n  override get canRetryRead(): boolean {\n    return !this.hasWriteStage;\n  }\n\n  addToPipeline(stage: Document): void {\n    this.pipeline.push(stage);\n  }\n\n  override buildCommandDocument(): Document {","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/mongodb/node-mongodb-native/blob/dce7939f86fb283e167ad709955abedb7bf23124/src/operations/aggregate.ts#L66-L102","documentation":"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.","triggerScenarios":"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.","commonSituations":"Wrapping batchSize incorrectly: aggregate(pipeline, { cursor: { batchSize } }) is right, but aggregate(pipeline, { cursor: batchSize }) is wrong. Plain-JS code that constructs options dynamically.","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."],"exampleFix":"// before\nconst cur = collection.aggregate(pipeline, { cursor: 100 });\n\n// after\nconst cur = collection.aggregate(pipeline, { cursor: { batchSize: 100 } });\n// or simply\nconst cur = collection.aggregate(pipeline, { batchSize: 100 });","handlingStrategy":"type-guard","validationCode":"if (options?.cursor != null && (typeof options.cursor !== 'object' || Array.isArray(options.cursor))) {\n  throw new TypeError('aggregate cursor option must be a plain object');\n}\nawait collection.aggregate(pipeline, options);","typeGuard":"const isCursorOptions = (v: unknown): v is { batchSize?: number } =>\n  v != null && typeof v === 'object' && !Array.isArray(v);","tryCatchPattern":null,"preventionTips":["Prefer the top-level batchSize option; let the driver build the cursor object.","Lint aggregate() calls for primitive cursor values."],"tags":["aggregation","cursor","api-misuse"],"backgroundTag":null,"analyzedSha":"dce7939f86fb283e167ad709955abedb7bf23124","analyzedAt":"2026-08-11T04:54:53.215Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}