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

  1. Pass a plain object as the filter, e.g. collection.find({ status: 'active' }).
  2. To query by _id, pass an ObjectId: collection.find(new ObjectId(id)) or collection.find({ _id: new ObjectId(id) }).
  3. Coerce dynamic filter input to a plain object before calling find, rejecting arrays explicitly.
  4. 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

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


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)