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 collection

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Coerce to an array: collection.aggregate(Array.isArray(pipeline) ? pipeline : [pipeline]).
  2. Default the local variable: const pipeline = rawPipeline ?? [];
  3. Guard with Array.isArray and surface a caller-side error.
  4. 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

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


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