mongodb/node-mongodb-native · error · MongoInvalidArgumentError
Query filter must be a plain object or ObjectId
Error message
Query filter must be a plain object or ObjectId
What it means
Thrown as a MongoInvalidArgumentError by the FindOperation constructor when the filter argument is not an object or is an array. The find command requires a plain query document (a BSON object); arrays, primitives, and null are rejected. An ObjectId is also accepted because the constructor special-cases it into {_id: ObjectId}.
Solutions
- Pass a plain object as the filter, e.g. collection.find({ status: 'active' }).
- To query by _id, pass an ObjectId: collection.find(new ObjectId(id)) or collection.find({ _id: new ObjectId(id) }).
- Coerce dynamic filter input to a plain object before calling find, rejecting arrays explicitly.
- If you intended an $or query, wrap conditions in { $or: [...] } rather than passing the bare array.
Example fix
// before
const doc = await collection.findOne('64abc...'); // string filter -> error
// after
const { ObjectId } = require('bson');
const doc = await collection.findOne({ _id: new ObjectId('64abc...') }); Defensive patterns
Strategy: type-guard
Validate before calling
function isPlainObject(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !Array.isArray(v);
}
if (!isPlainObject(filter) && !(filter instanceof ObjectId)) {
throw new TypeError('filter must be a plain object or ObjectId');
}
await collection.find(filter as any).toArray(); Type guard
function isFindFilter(v: unknown): v is Record<string, unknown> | ObjectId {
if (v instanceof ObjectId) return true;
return typeof v === 'object' && v !== null && !Array.isArray(v);
} Prevention
- Always pass a plain object or an ObjectId as the find filter.
- Wrap incoming JSON filter input with a type guard before querying.
- Use { _id: new ObjectId(id) } rather than a raw string when targeting by id.
When it happens
Trigger: Calling collection.find(), collection.findOne(), or any path that builds a FindOperation with a filter that is a string, number, boolean, array, or undefined that bypassed TypeScript types (e.g. from untyped JSON input). The check at find.ts:100 is `typeof filter !== 'object' || Array.isArray(filter)`.
Common situations: Passing a raw id string instead of an ObjectId or {_id: ...}; deserializing a filter from JSON where an array was supplied; dynamically building a filter variable that ends up as an array of conditions instead of an object.
Related errors
- Argument "filter" must be an object
- Argument "docs" must be an array of documents
- Argument "operations" must be an array of documents
- Argument "pipeline" must be an array of aggregation stages
- Argument "replacement" must be an object
AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11).
Data as JSON: /api/errors/41891d6f0c38ec6a.
Report an issue: GitHub.
Appendix: source
Thrown at src/operations/find.ts:101
/**
* @remarks WriteConcern can still be present on the options because
* we inherit options from the client/db/collection. The
* key must be present on the options in order to delete it.
* This allows typescript to delete the key but will
* not allow a writeConcern to be assigned as a property on options.
*/
override options: FindOptions & { writeConcern?: never };
filter: Document;
constructor(ns: MongoDBNamespace, filter: Document = {}, options: FindOptions = {}) {
super(undefined, options);
this.options = { ...options };
delete this.options.writeConcern;
this.ns = ns;
if (typeof filter !== 'object' || Array.isArray(filter)) {
throw new MongoInvalidArgumentError('Query filter must be a plain object or ObjectId');
}
// special case passing in an ObjectId as a filter
this.filter = filter != null && filter._bsontype === 'ObjectId' ? { _id: filter } : filter;
this.SERVER_COMMAND_RESPONSE_TYPE = this.explain ? ExplainedCursorResponse : CursorResponse;
}
override get commandName() {
return 'find' as const;
}
override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions {
return {
...this.options,
...this.bsonOptions,
documentsReturnedIn: 'firstBatch',
session: this.session,View on GitHub (pinned to dce7939f86)