The Short Answer
To filter vector-search or retriever results by geographic location, add an
attribute_filter stage with one of three geospatial operators over a location field: geo_radius (within N meters of a point), geo_bounding_box (inside a lat/lon box), or geo_polygon (inside an arbitrary shape). Radius is always in meters. Because it is a real filter stage, geo composes with the rest of a retriever: you can run a semantic vector query, keep only the results within 5km of a location, and apply a metadata filter, all in one request. A document with no location field is simply a non-match; malformed geometry is rejected at request time.The three operators
Each is a condition inside an
attribute_filter stage. The field (location here) holds a geographic point.// geo_radius — within 5,000 meters (5 km) of a center point (haversine)
{ "field": "location", "operator": "geo_radius",
"value": { "center": { "lat": 48.8584, "lon": 2.2945 }, "radius": 5000 } }
// geo_bounding_box — top_left (NW) and bottom_right (SE) corners
{ "field": "location", "operator": "geo_bounding_box",
"value": { "top_left": { "lat": 49.0, "lon": 2.0 },
"bottom_right": { "lat": 48.0, "lon": 3.0 } } }
// geo_polygon — an exterior ring of >= 3 points (auto-closed)
{ "field": "location", "operator": "geo_polygon",
"value": { "exterior": { "points": [
{ "lat": 48.0, "lon": 2.0 }, { "lat": 49.0, "lon": 2.0 },
{ "lat": 49.0, "lon": 3.0 }, { "lat": 48.0, "lon": 3.0 } ] } } }5000. Distances use the haversine formula on a spherical earth, which is accurate to a fraction of a percent at city and metro scale.lat/lon objects vs GeoJSON arrays: the one gotcha
A point can be written two ways, and one of them trips people up:
| Form | Example | Order |
|---|---|---|
| Object (recommended) | { "lat": 40.7128, "lon": -74.0060 } | named, unambiguous |
| GeoJSON array | [-74.0060, 40.7128] | lon first, then lat |
Lead with the object form: it names each value, so nobody has to remember an order. The array form follows the GeoJSON convention of longitude first: the reverse of how people say "lat, long" out loud, which is the single most common geo bug. If you store points as arrays, store them lon-first; if you are not sure, use the object form. A field can also hold a *list* of points (for example, every store in a chain), in which case the document matches if any of its points satisfies the operator.
Which operator should I use: radius, bounding box, or polygon?
| Operator | Use when… | Shape it expresses |
|---|---|---|
geo_radius | "within X of here": proximity, delivery range, "near me" | A circle around a point |
geo_bounding_box | a rectangular region, map viewport, or quick metro-area cut | An axis-aligned rectangle (antimeridian-safe) |
geo_polygon | real boundaries: a neighborhood, delivery zone, sales territory, school district | Any closed shape you can trace |
Rule of thumb: reach for radius for anything distance-based ("stores within 10km"), bounding box when a rectangle is good enough and you want the simplest possible region (a map's current view), and polygon only when the boundary is genuinely irregular and a circle or box would include or exclude the wrong places. A polygon of a delivery zone is worth the extra points; a polygon approximating a circle is not: use radius.
Combining geo with semantic search and metadata (location-aware RAG)
The reason geo lives in a retriever stage rather than a separate endpoint is composition. A single retriever can: run a semantic/vector query over your content, filter to a radius, and apply a metadata constraint, so "find listings that *look like* this photo, within 3km of downtown, under $2,000/month" is one request, not three round-trips and a manual intersection. This is the filtered vector search pattern with a geographic predicate, and it is what makes location-aware RAG and recommendations possible: the language model only ever sees candidates that are both semantically relevant and in the right place.
How fast is it, and where does it fit?
Be honest with yourself about scope: geospatial filtering runs as a scan over the candidate set your retriever produces, not a planet-scale geo index. In practice that is exactly what you want for the common case: you are refining a retrieved or metadata-narrowed set down to the ones in the right place, precisely. The pattern that stays fast: pair geo with a selective metadata filter or a vector query first, so geo evaluates over a focused candidate set rather than an entire large collection. Think "precise geo filtering on your retrieved results," not "the primary index for a billion global points." (Perf characteristics are being measured; this guide states behavior, not benchmark numbers.)
What can you build with it?
geo_radius around the user's location to find the nearest branches, ranked by the semantic or attribute signal you already search on.geo_bounding_box or geo_polygon.Doing it in Mixpeek
Two steps. First, put a
location field on your documents: a { "lat", "lon" } object (or a lon-first GeoJSON array) in the payload, alongside whatever else you index. Then add the operator to a retriever's attribute_filter stage next to your feature_search and metadata filters; the filter operators reference has the exact request shapes and a worked example, and the retriever stage catalog shows where the filter sits in a multi-stage pipeline. Geo works the same whether you run managed indexing or bring your own vectors to MVS: it is a filter over stored payloads, so any namespace with a location field can use it. For the broader picture of how filters interact with the vector search stage, see filtered vector search: pre-, post-, and in-place.