mongodb/node-mongodb-native · error · MongoInvalidArgumentError
Argument "pipeline" must be an array of aggregation stages
Error message
Argument "pipeline" must be an array of aggregation stages
What it means
Thrown by Collection.aggregate() when the pipeline argument is not an Array. The aggregation framework requires an ordered array of stage objects (e.g. [{$match: ...}, {$group: ...}]). This MongoInvalidArgumentError is raised synchronously before the AggregationCursor is constructed; no server round-trip occurs.
Source
Thrown at src/collection.ts:1063
filter,
update,
resolveOptions(this, options)
) as TODO_NODE_3286
);
}
/**
* Execute an aggregation framework pipeline against the collection, needs MongoDB \>= 2.2
*
* @param pipeline - An array of aggregation pipelines to execute
* @param options - Optional settings for the command
*/
aggregate<T extends Document = Document>(
pipeline: Document[] = [],
options?: AggregateOptions & Abortable
): AggregationCursor<T> {
if (!Array.isArray(pipeline)) {
throw new MongoInvalidArgumentError(
'Argument "pipeline" must be an array of aggregation stages'
);
}
return new AggregationCursor(
this.client,
this.s.namespace,
pipeline,
resolveOptions(this, options)
);
}
/**
* Create a new Change Stream, watching for new changes (insertions, updates, replacements, deletions, and invalidations) in this collection.
*
* @remarks
* watch() accepts two generic arguments for distinct use cases:
* - The first is to override the schema that may be defined for this specific collectionView on GitHub (pinned to 3366c21a63)
Solutions
- Coerce to an array: collection.aggregate(Array.isArray(pipeline) ? pipeline : [pipeline]).
- Default the local variable: const pipeline = rawPipeline ?? [];
- Guard with Array.isArray and surface a caller-side error.
- Type the builder's return as Document[] so TypeScript catches regressions.
Example fix
// before
const stage = filter ? { $match: filter } : null;
await collection.aggregate(stage).toArray();
// after
const pipeline = [];
if (filter) pipeline.push({ $match: filter });
await collection.aggregate(pipeline).toArray(); Defensive patterns
Strategy: type-guard
Validate before calling
function asPipeline(stages: unknown): Record<string, unknown>[] {
if (stages == null) return [];
if (!Array.isArray(stages)) return [stages as Record<string, unknown>];
return stages as Record<string, unknown>[];
}
await collection.aggregate(asPipeline(raw)).toArray(); Type guard
const isPipeline = (v: unknown): v is Record<string, unknown>[] => Array.isArray(v) && v.every(s => s != null && typeof s === 'object' && !Array.isArray(s));
Try / catch
try {
await collection.aggregate(pipeline).toArray();
} catch (e) {
if (e instanceof MongoInvalidArgumentError && /pipeline.*must be an array/.test(e.message)) {
throw new TypeError('Aggregation pipeline must be an array of stages');
}
throw e;
} Prevention
- Default pipeline variables to [].
- Type pipeline builders as Document[][].
- Wrap single stages in [] rather than passing them bare.
When it happens
Trigger: Passing a single stage object instead of an array; passing undefined (note pipeline defaults to [] but an explicit non-array still fails); passing a comma-separated string or an object keyed by stage names; passing a generator function instead of its result.
Common situations: Building a pipeline conditionally and ending up with undefined when no stages apply; copy-pasting a stage without wrapping in []; mis-typing a config object as { $match: {...} } rather than [{ $match: {...} }].
Related errors
- Argument "docs" must be an array of documents
- Argument "operations" must be an array of documents
- Update operations require that all atomic operators have def
- Option "keyAltNames" must be an array of strings, but was of
- Option "keyAltNames" must be an array of strings, but item a
AI-assisted analysis of mongodb/node-mongodb-native@3366c21a63 (2026-08-04).
Data as JSON: /data/errors/3c9b0f0bbb65d974.json.
Report an issue: GitHub.