Automattic/mongoose · error · TypeError

$near must be either an array or an object with a $geometry

Error message

$near must be either an array or an object with a $geometry property

What it means

cast$near (used by $near and $nearSphere on geospatial paths) accepts three shapes: a legacy coordinate array ([x, y]), an object with a $geometry key (GeoJSON, optionally with $maxDistance/$minDistance), or an array value handled by the array caster. Anything else — a string, a plain { lat, lng } object without $geometry, null — falls through to this TypeError.

Source

Thrown at lib/schema/operators/geospatial.js:33

exports.cast$near = cast$near;
exports.cast$within = cast$within;

function cast$near(val) {
  const SchemaArray = require('../array');

  if (Array.isArray(val)) {
    castArraysOfNumbers(val, this);
    return val;
  }

  _castMinMaxDistance(this, val);

  if (val?.$geometry) {
    return cast$geometry(val, this);
  }

  if (!Array.isArray(val)) {
    throw new TypeError('$near must be either an array or an object ' +
      'with a $geometry property');
  }

  return SchemaArray.prototype.castForQuery.call(this, null, val);
}

function cast$geometry(val, self) {
  switch (val.$geometry.type) {
    case 'Polygon':
    case 'LineString':
    case 'Point':
      castArraysOfNumbers(val.$geometry.coordinates, self);
      break;
    default:
      // ignore unknowns
      break;
  }

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Use legacy pairs: `{ loc: { $near: [lng, lat] } }` — note longitude first
  2. Use proper GeoJSON: `{ loc: { $near: { $geometry: { type: 'Point', coordinates: [lng, lat] }, $maxDistance: 5000 } } }`
  3. Normalize client coordinates (lat/lng objects or strings) into one of the two accepted shapes in your query-building layer
  4. For $geoNear aggregation, use its near/distanceField options instead

Example fix

// before
Model.find({ loc: { $near: { lat: 34.05, lng: -118.24 } } });

// after
Model.find({ loc: { $near: { $geometry: { type: 'Point', coordinates: [-118.24, 34.05] }, $maxDistance: 5000 } } });
Defensive patterns

Strategy: validation

Validate before calling

function toNearFilter(coords /* { lng, lat } or [lng, lat] */, maxDistance) {
  const point = Array.isArray(coords) ? coords : [coords.lng, coords.lat];
  if (typeof point[0] !== 'number' || typeof point[1] !== 'number') {
    throw new Error('$near needs [lng, lat] numbers or { $geometry }');
  }
  return { $near: { $geometry: { type: 'Point', coordinates: point }, $maxDistance: maxDistance } };
}

Type guard

function isValidNear(v) {
  return Array.isArray(v) || (v != null && typeof v === 'object' && v.$geometry != null);
}

Try / catch

try { await Model.find({ loc: { $near: value } }); } catch (err) { if (/\$near must be either an array/.test(err.message)) { return badRequest('$near requires [lng, lat] or { $geometry: ... }'); } throw err; }

Prevention

When it happens

Trigger: `Model.find({ loc: { $near: '12,34' } })` (string), `{ loc: { $near: { lat: 12, lng: 34 } } }` (missing $geometry wrapper), `{ loc: { $near: {} } }`. Note: an object with only $maxDistance/$minDistance but no $geometry also reaches the throw.

Common situations: Sending Google-Maps-style { lat, lng } objects instead of GeoJSON; forgetting the $geometry wrapper around GeoJSON; confusing $near query syntax with the $geoNear aggregation stage; clients sending 'lat,lng' strings from URL query params.

Related errors


AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21). Data as JSON: /api/errors/da9579d93b9e6097. Report an issue: GitHub.