> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mixpeek.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Attribute Filter

> Filter documents by metadata field conditions with boolean logic support

<Frame>
  <img src="https://mintcdn.com/mixpeek/TwtTrae3Fi3EFJ72/assets/retrievers/attribute-filter.svg?fit=max&auto=format&n=TwtTrae3Fi3EFJ72&q=85&s=8d3295fc1e60c387cdab775efc4fb901" alt="Attribute Filter stage showing metadata-based document filtering" width="1000" height="400" data-path="assets/retrievers/attribute-filter.svg" />
</Frame>

The Attribute Filter stage filters documents based on metadata field conditions. It supports simple single-field filtering and complex boolean logic (AND/OR/NOT). When used as a first stage, it retrieves documents directly from the database; when used after other stages, it filters in-memory results.

<Note>
  **Stage Category**: FILTER (Reduces document set)

  **Transformation**: N documents → M documents (where M ≤ N, based on conditions)
</Note>

## When to Use

| Use Case                   | Description                                   |
| -------------------------- | --------------------------------------------- |
| **Metadata filtering**     | Filter by status, category, date, etc.        |
| **Post-search refinement** | Narrow semantic search results                |
| **Access control**         | Filter by user permissions                    |
| **Business logic**         | Active items, published content               |
| **Initial retrieval**      | Fetch documents by attributes (no embeddings) |

## When NOT to Use

| Scenario                | Recommended Alternative             |
| ----------------------- | ----------------------------------- |
| Semantic similarity     | `feature_search`                    |
| Content-based filtering | `llm_filter`                        |
| Complex text matching   | `feature_search` with text features |
| Scoring/ranking         | Use sort stages after filtering     |

## Parameters

### Simple Mode

Use for single-condition filtering:

| Parameter          | Type    | Default    | Description                      |
| ------------------ | ------- | ---------- | -------------------------------- |
| `field`            | string  | *Required* | Field path to filter on          |
| `operator`         | string  | `eq`       | Comparison operator              |
| `value`            | any     | *Required* | Value to compare against         |
| `case_insensitive` | boolean | `false`    | Case-insensitive string matching |

### Boolean Mode

Use for complex multi-condition filtering:

| Parameter    | Type    | Default    | Description                            |
| ------------ | ------- | ---------- | -------------------------------------- |
| `conditions` | object  | *Required* | Boolean condition object               |
| `batch_size` | integer | `100`      | Documents per batch (first-stage only) |

### Natural-Language Mode

Supply a plain-English filter and let an LLM compile it to a structured `conditions` tree at runtime. Useful when the filter shape is derived from user input or when field names are unknown in advance.

| Parameter          | Type          | Default    | Description                                                                             |
| ------------------ | ------------- | ---------- | --------------------------------------------------------------------------------------- |
| `natural_language` | string        | *Required* | Plain-English filter description (e.g. `"active premium customers with priority >= 5"`) |
| `field_hints`      | list\[string] | `null`     | Optional list of allowed field paths to constrain the LLM's output                      |

<Info>
  NL mode rides the wave — better LLMs produce better filter trees. Takes precedence over simple-mode fields; ignored if `conditions` is also set. Adds one LLM call per execution (\~200-500ms).
</Info>

## Supported Operators

| Operator           | Description                                                   | Example Value                                                                            |
| ------------------ | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `eq`               | Equals                                                        | `"active"`, `42`, `true`                                                                 |
| `ne`               | Not equals                                                    | `"deleted"`                                                                              |
| `gt`               | Greater than                                                  | `100`                                                                                    |
| `gte`              | Greater than or equal                                         | `4.5`                                                                                    |
| `lt`               | Less than                                                     | `50`                                                                                     |
| `lte`              | Less than or equal                                            | `10`                                                                                     |
| `in`               | In array                                                      | `["tech", "science"]`                                                                    |
| `nin`              | Not in array                                                  | `["spam", "deleted"]`                                                                    |
| `contains`         | Contains substring                                            | `"guide"`                                                                                |
| `starts_with`      | Starts with                                                   | `"intro"`                                                                                |
| `ends_with`        | Ends with                                                     | `".pdf"`                                                                                 |
| `regex`            | Regular expression                                            | `"^[A-Z].*"`                                                                             |
| `exists`           | Field exists                                                  | `true` or `false`                                                                        |
| `is_null`          | Field is null                                                 | `true` or `false`                                                                        |
| `text`             | Full-text search (token-based; word order not preserved)      | `"machine learning"`                                                                     |
| `phrase`           | Exact phrase — matches the words in order, word-boundary safe | `"machine learning model"`                                                               |
| `geo_radius`       | Within N meters of a center point                             | `{"center": {"lat": 40.758, "lon": -73.9855}, "radius": 15000}`                          |
| `geo_bounding_box` | Inside a lat/lon box (antimeridian-safe)                      | `{"top_left": {"lat": 42.0, "lon": -74.5}, "bottom_right": {"lat": 40.5, "lon": -72.5}}` |
| `geo_polygon`      | Inside an arbitrary polygon (≥3 points)                       | `{"exterior": {"points": [{"lat": 41.2, "lon": -74.5}, ...]}}`                           |

<Info>
  The three `geo_*` operators take an **object** as their `value`, not a scalar. See [Geospatial filtering](#geospatial-filtering) for the field format and exact value shapes.
</Info>

## Configuration Examples

<CodeGroup>
  ```json Simple Equality theme={null}
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "field": "metadata.status",
        "operator": "eq",
        "value": "published"
      }
    }
  }
  ```

  ```json Numeric Comparison theme={null}
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "field": "metadata.price",
        "operator": "lte",
        "value": 100
      }
    }
  }
  ```

  ```json In Array theme={null}
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "field": "metadata.category",
        "operator": "in",
        "value": ["electronics", "computers", "phones"]
      }
    }
  }
  ```

  ```json Contains Substring theme={null}
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "field": "metadata.title",
        "operator": "contains",
        "value": "guide",
        "case_insensitive": true
      }
    }
  }
  ```

  ```json Field Exists theme={null}
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "field": "metadata.premium_features",
        "operator": "exists",
        "value": true
      }
    }
  }
  ```

  ```json Dynamic Value theme={null}
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "field": "metadata.category",
        "operator": "eq",
        "value": "{{INPUT.category}}"
      }
    }
  }
  ```

  ```json Natural Language theme={null}
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "natural_language": "published blog posts from 2024 that are not archived",
        "field_hints": ["metadata.status", "metadata.published_at", "metadata.archived"]
      }
    }
  }
  ```
</CodeGroup>

## Boolean Conditions

For complex filtering, use the `conditions` parameter with AND/OR/NOT logic:

<CodeGroup>
  ```json AND Conditions theme={null}
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "conditions": {
          "AND": [
            {"field": "metadata.status", "operator": "eq", "value": "active"},
            {"field": "metadata.in_stock", "operator": "eq", "value": true},
            {"field": "metadata.price", "operator": "lte", "value": 500}
          ]
        }
      }
    }
  }
  ```

  ```json OR Conditions theme={null}
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "conditions": {
          "OR": [
            {"field": "metadata.category", "operator": "eq", "value": "featured"},
            {"field": "metadata.rating", "operator": "gte", "value": 4.5}
          ]
        }
      }
    }
  }
  ```

  ```json NOT Conditions theme={null}
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "conditions": {
          "NOT": [
            {"field": "metadata.status", "operator": "eq", "value": "deleted"},
            {"field": "metadata.spam", "operator": "eq", "value": true}
          ]
        }
      }
    }
  }
  ```

  ```json Nested Boolean Logic theme={null}
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "conditions": {
          "AND": [
            {"field": "metadata.status", "operator": "eq", "value": "active"},
            {
              "OR": [
                {"field": "metadata.category", "operator": "eq", "value": "premium"},
                {"field": "metadata.featured", "operator": "eq", "value": true}
              ]
            },
            {
              "NOT": [
                {"field": "metadata.region", "operator": "eq", "value": "restricted"}
              ]
            }
          ]
        }
      }
    }
  }
  ```
</CodeGroup>

## Geospatial filtering

Filter documents by geographic location. Three operators — `geo_radius`,
`geo_bounding_box`, and `geo_polygon` — restrict results to documents whose
location falls within a circle, a box, or an arbitrary polygon. They work in
both simple mode and inside boolean `conditions`, exactly like any other
operator.

### The location field

A document is geo-filterable when it carries a location field. By convention the
field is named `location`, and Mixpeek resolves it wherever it lives — it tries
top-level `location`, then `metadata.location`, then `_internal.metadata.location`
— so you write `"field": "location"` regardless of how the document was ingested.

A location value can be either of two forms:

| Form          | Example                            | Notes                                                      |
| ------------- | ---------------------------------- | ---------------------------------------------------------- |
| Object        | `{"lat": 40.758, "lon": -73.9855}` | Recommended. `lat` in `[-90, 90]`, `lon` in `[-180, 180]`. |
| GeoJSON array | `[-73.9855, 40.758]`               | **Longitude first**, then latitude — the GeoJSON order.    |

<Warning>
  **GeoJSON arrays are `[lon, lat]`, not `[lat, lon]`.** This is the single most
  common geo footgun. `[-73.9855, 40.758]` is Manhattan; `[40.758, -73.9855]` is
  an invalid point (latitude 40.758 is fine, but longitude 40.758 places it in
  the Indian Ocean, and if either value exceeds its range the point is dropped).
  Prefer the `{"lat": ..., "lon": ...}` object form, which is unambiguous.
</Warning>

A document with **no** location field never matches a geo filter (it is treated
as a non-match, not an error). If a document stores a **list** of points, it
matches when **any** point satisfies the filter.

### The three operators

<CodeGroup>
  ```json geo_radius theme={null}
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "field": "location",
        "operator": "geo_radius",
        "value": {
          "center": {"lat": 40.758, "lon": -73.9855},
          "radius": 15000
        }
      }
    }
  }
  ```

  ```json geo_bounding_box theme={null}
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "field": "location",
        "operator": "geo_bounding_box",
        "value": {
          "top_left": {"lat": 42.0, "lon": -74.5},
          "bottom_right": {"lat": 40.5, "lon": -72.5}
        }
      }
    }
  }
  ```

  ```json geo_polygon theme={null}
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "field": "location",
        "operator": "geo_polygon",
        "value": {
          "exterior": {
            "points": [
              {"lat": 41.2, "lon": -74.5},
              {"lat": 41.2, "lon": -73.5},
              {"lat": 40.4, "lon": -73.5},
              {"lat": 40.4, "lon": -74.5}
            ]
          }
        }
      }
    }
  }
  ```
</CodeGroup>

| Operator           | `value` shape                                                  | Semantics                                                                                                   |
| ------------------ | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `geo_radius`       | `{"center": {"lat", "lon"}, "radius": <meters>}`               | Within `radius` **meters** of `center`. Distance is haversine great-circle (mean Earth radius 6,371,000 m). |
| `geo_bounding_box` | `{"top_left": {"lat", "lon"}, "bottom_right": {"lat", "lon"}}` | Inside the box. Antimeridian-safe: if `top_left.lon > bottom_right.lon` the box wraps 180° (e.g. Fiji).     |
| `geo_polygon`      | `{"exterior": {"points": [{"lat", "lon"}, ...]}}`              | Inside the exterior ring by ray-casting. Needs **≥ 3** points.                                              |

<Note>
  `radius` is always in **meters** — `15000` is 15 km, not 15,000 km.
</Note>

### Combine with a selective filter (recommended)

Geo operators are evaluated by a payload scan — they are correct but **not
index-accelerated** (there is no geo postings index yet). To keep the candidate
set small and latency low, pair a geo condition with a selective non-geo
condition (a brand, category, or status equality) inside an `AND`:

```json theme={null}
{
  "stage_name": "attribute_filter",
  "stage_type": "filter",
  "config": {
    "stage_id": "attribute_filter",
    "parameters": {
      "conditions": {
        "AND": [
          {"field": "metadata.category", "operator": "eq", "value": "store"},
          {
            "field": "location",
            "operator": "geo_radius",
            "value": {"center": {"lat": 40.758, "lon": -73.9855}, "radius": 5000}
          }
        ]
      }
    }
  }
}
```

The selective `eq` narrows the set first; the geo condition is then applied to a
small candidate list. Don't rely on a geo-only filter over a large collection
for sub-millisecond latency.

### Validation

Malformed geo specs fail **loudly at request time** with a message that names
the expected shape — you get a clear `400`, not a silent empty result set.
Common mistakes that are rejected:

* `value` is not an object (e.g. a bare number or string)
* `center` / `top_left` / `bottom_right` missing `lat` or `lon`, or out of range
* `radius` missing or negative
* `geo_polygon` with fewer than 3 points, or a point missing `lat`/`lon`

### Worked example

Six documents, each an office with a `location` (one has none):

| Document     | Location        | lat, lon          |
| ------------ | --------------- | ----------------- |
| Manhattan    | NYC             | 40.7580, -73.9855 |
| Brooklyn     | NYC             | 40.6782, -73.9442 |
| Hartford     | CT              | 41.7658, -72.6734 |
| Boston       | MA              | 42.3601, -71.0589 |
| Philadelphia | PA              | 39.9526, -75.1652 |
| Virtual      | *(no location)* | —                 |

Applying each operator from the examples above:

| Filter                                       | Matches                           | Why                                                                                                   |
| -------------------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `geo_radius` center=Manhattan, 15 km         | **Manhattan, Brooklyn**           | Brooklyn is \~8 km away; Hartford (\~160 km), Boston (\~306 km), Philadelphia (\~130 km) are outside. |
| `geo_bounding_box` (42.0,-74.5)→(40.5,-72.5) | **Manhattan, Brooklyn, Hartford** | Boston's lon -71.06 is east of -72.5; Philadelphia's lat 39.95 is south of 40.5.                      |
| `geo_polygon` NYC-metro rectangle            | **Manhattan, Brooklyn**           | Hartford's lat 41.77 and Philadelphia's lat 39.95 fall outside the ring.                              |

In every case the Virtual office (no location) is excluded.

## First-Stage vs Later-Stage Behavior

| Position        | Behavior                                                              |
| --------------- | --------------------------------------------------------------------- |
| **First stage** | Fetches documents directly from database (up to 1,000 per collection) |
| **Later stage** | Filters in-memory results from previous stages                        |

### First-Stage Example

When no documents exist in the pipeline yet:

```json theme={null}
[
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "field": "metadata.status",
        "operator": "eq",
        "value": "published",
        "batch_size": 100
      }
    }
  }
]
```

### Later-Stage Example

After semantic search:

```json theme={null}
[
  {
    "stage_name": "feature_search",
    "stage_type": "filter",
    "config": {
      "stage_id": "feature_search",
      "parameters": {
        "searches": [{"feature_uri": "mixpeek://multimodal_extractor@v1/vertex_multimodal_embedding", "query": "{{INPUT.query}}"}],
        "final_top_k": 100
      }
    }
  },
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "field": "metadata.in_stock",
        "operator": "eq",
        "value": true
      }
    }
  }
]
```

## Performance

| Metric                  | Value                          |
| ----------------------- | ------------------------------ |
| **Latency**             | 5-20ms                         |
| **First-stage limit**   | 1,000 documents per collection |
| **In-memory filtering** | \< 5ms for 1,000 docs          |
| **Index utilization**   | Uses indexes when available    |

<Tip>
  For best performance with `feature_search`, use pre-filters in the search stage instead of a separate `attribute_filter` stage. Pre-filters are applied at the vector index level.
</Tip>

## Common Pipeline Patterns

### Search + Filter + Sort

```json theme={null}
[
  {
    "stage_name": "feature_search",
    "stage_type": "filter",
    "config": {
      "stage_id": "feature_search",
      "parameters": {
        "searches": [
          {
            "feature_uri": "mixpeek://multimodal_extractor@v1/vertex_multimodal_embedding",
            "query": "{{INPUT.query}}",
            "top_k": 100
          }
        ],
        "final_top_k": 50
      }
    }
  },
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "conditions": {
          "AND": [
            {"field": "metadata.status", "operator": "eq", "value": "active"},
            {"field": "metadata.category", "operator": "in", "value": "{{INPUT.categories}}"}
          ]
        }
      }
    }
  },
  {
    "stage_name": "sort_attribute",
    "stage_type": "sort",
    "config": {
      "stage_id": "sort_attribute",
      "parameters": {
        "field": "metadata.created_at",
        "direction": "desc"
      }
    }
  }
]
```

### Attribute-Only Retrieval (No Embeddings)

```json theme={null}
[
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "conditions": {
          "AND": [
            {"field": "metadata.type", "operator": "eq", "value": "product"},
            {"field": "metadata.featured", "operator": "eq", "value": true}
          ]
        },
        "batch_size": 50
      }
    }
  },
  {
    "stage_name": "sort_attribute",
    "stage_type": "sort",
    "config": {
      "stage_id": "sort_attribute",
      "parameters": {
        "field": "metadata.priority",
        "direction": "desc"
      }
    }
  },
  {
    "stage_name": "sample",
    "stage_type": "reduce",
    "config": {
      "stage_id": "sample",
      "parameters": {
        "count": 10
      }
    }
  }
]
```

### Multi-Stage Filtering

```json theme={null}
[
  {
    "stage_name": "feature_search",
    "stage_type": "filter",
    "config": {
      "stage_id": "feature_search",
      "parameters": {
        "searches": [{"feature_uri": "mixpeek://multimodal_extractor@v1/vertex_multimodal_embedding", "query": "{{INPUT.query}}"}],
        "final_top_k": 200
      }
    }
  },
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "field": "metadata.category",
        "operator": "eq",
        "value": "{{INPUT.category}}"
      }
    }
  },
  {
    "stage_name": "rerank",
    "stage_type": "sort",
    "config": {
      "stage_id": "rerank",
      "parameters": {
        "inference_name": "BAAI__bge_reranker_v2_m3",
        "top_k": 20
      }
    }
  },
  {
    "stage_name": "attribute_filter",
    "stage_type": "filter",
    "config": {
      "stage_id": "attribute_filter",
      "parameters": {
        "field": "metadata.in_stock",
        "operator": "eq",
        "value": true
      }
    }
  }
]
```

## Comparison: attribute\_filter vs feature\_search Pre-Filters

| Aspect           | attribute\_filter (stage)             | feature\_search pre-filters |
| ---------------- | ------------------------------------- | --------------------------- |
| **When applied** | After search results                  | During vector search        |
| **Performance**  | Good                                  | Best (index-level)          |
| **Use case**     | Post-filtering, first-stage retrieval | Always when possible        |
| **Flexibility**  | Can be placed anywhere                | Only with feature\_search   |

**Recommendation:** When filtering during semantic search, prefer pre-filters in `feature_search`. Use `attribute_filter` for:

* Post-search refinement based on previous stage outputs
* First-stage attribute-only retrieval
* Dynamic filters that depend on earlier stage results

## Error Handling

| Error                          | Behavior                                                                                              |
| ------------------------------ | ----------------------------------------------------------------------------------------------------- |
| Field not found                | Document excluded (treated as no match)                                                               |
| Invalid operator               | Stage fails with error                                                                                |
| Type mismatch                  | Attempts type coercion, then excludes                                                                 |
| Invalid regex                  | Stage fails with error                                                                                |
| Malformed geo spec             | Request rejected with a clear error naming the expected shape (fails loud, not a silent empty result) |
| Document has no location field | Document excluded from geo filters (treated as no match)                                              |

## Related Stages

* [Feature Search](/docs/retrieval/stages/feature-search) - Semantic vector search
* [LLM Filter](/docs/retrieval/stages/llm-filter) - Content-based LLM filtering
* [Sort Attribute](/docs/retrieval/stages/sort-attribute) - Sort by field values
