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

The FindOperation constructor (src/operations/find.ts:101) validates that the `filter` argument is a plain object (typeof 'object' and not an array). If a non-object or an array is passed, a MongoInvalidArgumentError is thrown before any server interaction. This is a client-side guard; the special case of passing an ObjectId is handled separately (it is wrapped into { _id: filter }).

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 3366c21a63)

Solutions

  1. Pass a plain object as the filter, e.g. collection.find({ status: 'active' }).
  2. To query by _id, use collection.find({ _id: new ObjectId(id) }) or pass the ObjectId directly.
  3. For multiple values, use $in: collection.find({ sku: { $in: ['a','b'] } }).

Example fix

// before
const cursor = collection.find('abc'); // string, not an object

// after
const cursor = collection.find({ name: 'abc' });
// or by id:
const cursor = collection.find({ _id: new ObjectId('abc...') });
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);
}
// usage before find:
if (!isPlainObject(filter)) {
  throw new TypeError('filter must be a plain object or ObjectId');
}

Type guard

const isPlainObjectFilter = (f: unknown): boolean =>
  (typeof f === 'object' && f !== null && !Array.isArray(f)) ||
  (f != null && (f as any)._bsontype === 'ObjectId');

Prevention

When it happens

Trigger: Calling collection.find('string'), collection.find(42), collection.find([1,2]), or collection.find(null) — any filter that is not a plain object or an ObjectId.

Common situations: Passing a value where a query document is expected — e.g. passing an ID string instead of {_id: id}, or passing an array of values hoping for an $in shorthand.

Related errors


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