mongodb/laravel-mongodb · error · InvalidArgumentException
Only read aggregation stages are allowed. Found
Error message
Only read aggregation stages are allowed. Found: %s
What it means
A stage in the aggregate pipeline (top-level or nested inside $facet/$lookup/$unionWith) is not on the library's allow list of read-only aggregation stages. The allow list deliberately excludes write stages ($merge, $out) and rejects any unknown/future stage by default, so the tool stays strictly read-only.
Solutions
- Remove $merge/$out stages — this tool is read-only; persist results via application code instead.
- Fix the stage name spelling and ensure each pipeline element is a single-key document like {"$match": {...}}.
- Re-express disallowed stages with allowed equivalents (e.g. $sortByCount is allowed; use $group + $sort).
- For stages genuinely missing from the allow list, run the aggregation in your own code with the MongoDB PHP library.
Example fix
// before
{"aggregate": "users", "pipeline": [{"$match": {"active": true}}, {"$out": "active_users"}]}
// after
{"aggregate": "users", "pipeline": [{"$match": {"active": true}}, {"$project": {"name": 1, "email": 1}}]} Defensive patterns
Strategy: validation
Validate before calling
$readStages = ['facet','lookup','unionWith','addFields','bucket','bucketAuto','changeStream','collStats','count','densify','documents','fill','geoNear','graphLookup','group','indexStats','limit','listLocalSessions','listSampledQueries','listSearchIndexes','listSessions','match','planCacheStats','project','redact','replaceRoot','replaceWith','sample','search','searchMeta','set','setWindowFields','shardedDataDistribution','skip','sort','sortByCount','unwind','unset','vectorSearch'];
foreach ($pipeline as $stage) {
$name = ltrim((string) array_key_first($stage), '$');
if (!in_array($name, $readStages, true)) {
throw new \InvalidArgumentException("Stage not allowed: $name");
}
} Type guard
$isReadStage = fn (array $stage): bool => in_array(ltrim((string) array_key_first($stage), '$'), [
'facet','lookup','unionWith','addFields','match','group','project','sort','limit','skip','unwind','set','unset','count','sample','replaceRoot','replaceWith','bucket','bucketAuto','densify','fill','geoNear','graphLookup','redact','setWindowFields','sortByCount','documents','vectorSearch','search','searchMeta','changeStream','collStats','indexStats','planCacheStats','currentOp','listLocalSessions','listSampledQueries','listSearchIndexes','listSessions','shardedDataDistribution','changeStreamSplitLargeEvent'
], true); Try / catch
try {
$result = $tool->handle($request);
} catch (\InvalidArgumentException $e) {
if (str_starts_with($e->getMessage(), 'Only read aggregation stages are allowed')) {
// remove/replace the offending stage (e.g. $merge, $out) and retry
}
} Prevention
- Never include $merge or $out in pipelines sent to this read-only tool.
- Ensure each pipeline element is a single-key document with the stage operator as the key.
- Double-check stage spelling ($match, not $math) before sending.
- Validate nested sub-pipelines inside $facet/$lookup/$unionWith too — the same allow list applies to them.
When it happens
Trigger: Including {"$merge": ...} or {"$out": ...} in the pipeline; a misspelled stage (e.g. "$math" instead of "$match", or "$sortByCount" with wrong casing); using an exotic/unsupported stage like "$indexStats" is fine but "$planCacheStats" variants or server-specific stages not on the list will fail; nesting a write stage inside a $facet/$lookup/$unionWith sub-pipeline.
Common situations: Trying to persist aggregation results with $out/$merge through the AI tool; typos in stage names; the first element of a stage object not being the stage operator (malformed stage document); copying stages from newer MongoDB versions that the allow list doesn't know yet.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Only read commands are allowed
- Aggregation nesting exceeds the maximum of
- A pipeline exceeds the maximum of
- The relation key of type
- Please pass a valid MongoDB command
AI-assisted analysis of mongodb/laravel-mongodb@0634653039 (2026-09-15).
Data as JSON: /api/errors/f6245d507f2ea559.
Report an issue: GitHub.
Appendix: source
Thrown at src/Tools/DatabaseQuery.php:195
'set',
'setWindowFields',
'shardedDataDistribution',
'skip',
'sort',
'sortByCount',
'unwind',
'unset',
'vectorSearch',
]);
// A pipeline is a list of single-key stage documents, e.g. [['$match' => [...]], ...].
foreach ($pipeline as $stage) {
$operator = array_key_first($stage);
$stageName = str_replace('$', '', (string) $operator);
$stageBody = $stage[$operator];
if (! in_array($stageName, $allowList, true)) {
throw new InvalidArgumentException(sprintf('Only read aggregation stages are allowed. Found: %s', $stageName));
}
if (! in_array($stageName, $supportsNestedWrites, true)) {
continue;
}
switch ($stageName) {
case 'facet':
array_walk($stageBody, fn ($pipeline) => $this->ensureNoNestedWriteInAggregation($pipeline, $level + 1));
break;
case 'lookup':
case 'unionWith':
// Both stages take an optional single sub-pipeline; unionWith may also be a plain collection name.
if (is_array($stageBody) && isset($stageBody['pipeline'])) {
$this->ensureNoNestedWriteInAggregation($stageBody['pipeline'], $level + 1);
}View on GitHub (pinned to 0634653039)