---
title: Enhanced Metadata Filtering
description: Fine-grained metadata queries for precise OSS memory retrieval.
icon: "filter"
---

Enhanced metadata filtering in Mem0 lets you run complex queries across memory metadata. Combine comparisons, logical operators, and wildcard matches to zero in on the exact memories your agent needs.

---

## Feature anatomy

<AccordionGroup>
  <Accordion title="Operator quick reference">
    | Operator | Meaning | When to use it |
    | --- | --- | --- |
    | `eq` / `ne` | Equals / not equals | Exact matches on strings, numbers, or booleans. |
    | `gt` / `gte` | Greater than / greater than or equal | Rank results by score, confidence, or any numeric field. |
    | `lt` / `lte` | Less than / less than or equal | Cap numeric values (e.g., ratings, timestamps). |
    | `in` / `nin` | In list / not in list | Pre-approve or block sets of values without chaining multiple filters. |
    | `contains` / `icontains` | Case-sensitive / case-insensitive substring match | Scan text fields for keywords. |
    | `*` | Wildcard | Require that a field exists, regardless of value. |
    | `AND` / `OR` / `NOT` | Combine filters | Build logic trees so multiple conditions work together. |
  </Accordion>
</AccordionGroup>

### Metadata selectors

Start with key-value filters when you need direct matches on metadata fields.

```python
from mem0 import Memory

m = Memory()

# Search with simple metadata filters
results = m.search(
    "What are my preferences?",
    filters={"user_id": "alice", "category": "preferences"}
)
```

<Info icon="check">
  Expect only memories tagged with `category="preferences"` to return for the given `user_id`.
</Info>

### Comparison operators

Layer greater-than/less-than comparisons to rank results by score, confidence, or any numeric field. Equality helpers (`eq`, `ne`) keep string and boolean checks explicit.

```python
# Greater than / Less than
results = m.search(
    "recent activities",
    filters={
        "user_id": "alice",
        "score": {"gt": 0.8},
        "priority": {"gte": 5},
        "confidence": {"lt": 0.9},
        "rating": {"lte": 3}
    }
)

# Equality operators
results = m.search(
    "specific content",
    filters={
        "user_id": "alice",
        "status": {"eq": "active"},
        "archived": {"ne": True}
    }
)
```

### List-based operators

Use `in` and `nin` when you want to pre-approve or exclude specific values without writing multiple equality checks.

```python
# In / Not in operators
results = m.search(
    "multi-category search",
    filters={
        "user_id": "alice",
        "category": {"in": ["food", "travel", "entertainment"]},
        "status": {"nin": ["deleted", "archived"]}
    }
)
```

<Info icon="check">
  Verify the response includes only memories in the whitelisted categories and omits any with archived or deleted status.
</Info>

### String operators

`contains` and `icontains` capture substring matches, making it easy to scan descriptions or tags for keywords without retrieving irrelevant memories.

```python
# Text matching operators
results = m.search(
    "content search",
    filters={
        "user_id": "alice",
        "title": {"contains": "meeting"},
        "description": {"icontains": "important"},
        "tags": {"contains": "urgent"}
    }
)
```

### Wildcard matching

Allow any value for a field while still requiring the field to exist—handy when the mere presence of a field matters.

```python
# Match any value for a field
results = m.search(
    "all with category",
    filters={
        "user_id": "alice",
        "category": "*"
    }
)
```

### Logical combinations

Combine filters with `AND`, `OR`, and `NOT` to express complex decision trees. Nest logical operators to encode multi-branch workflows.

```python
# Logical AND
results = m.search(
    "complex query",
    filters={
        "user_id": "alice",
        "AND": [
            {"category": "work"},
            {"priority": {"gte": 7}},
            {"status": {"ne": "completed"}}
        ]
    }
)

# Logical OR
results = m.search(
    "flexible query",
    filters={
        "user_id": "alice",
        "OR": [
            {"category": "urgent"},
            {"priority": {"gte": 9}},
            {"deadline": {"contains": "today"}}
        ]
    }
)

# Logical NOT
results = m.search(
    "exclusion query",
    filters={
        "user_id": "alice",
        "NOT": [
            {"category": "archived"},
            {"status": "deleted"}
        ]
    }
)

# Complex nested logic
results = m.search(
    "advanced query",
    filters={
        "user_id": "alice",
        "AND": [
            {
                "OR": [
                    {"category": "work"},
                    {"category": "personal"}
                ]
            },
            {"priority": {"gte": 5}},
            {
                "NOT": [
                    {"status": "archived"}
                ]
            }
        ]
    }
)
```

<Info icon="check">
  Inspect the response metadata—each returned memory should satisfy the combined logic tree exactly. If results look too broad, log the raw filters sent to your vector store.
</Info>

---

## Configure it

Tune your vector store so filter-heavy queries stay fast. Index fields you frequently filter on and keep complex checks for later in the evaluation order.

```python
# Ensure your vector store supports indexing on filtered fields
config = {
    "vector_store": {
        "provider": "qdrant",
        "config": {
            "host": "localhost",
            "port": 6333,
            "indexed_fields": ["category", "priority", "status", "user_id"]
        }
    }
}
```

<Info icon="check">
  After enabling indexing, benchmark the same query—latency should drop once the store can prune documents on indexed fields before vector scoring.
</Info>

<Tip>
  Put simple key=value filters on indexed fields before your range or text conditions so the store trims results early.
</Tip>

```python
# More efficient: Filter on indexed fields first
good_filters = {
    "user_id": "alice",
    "AND": [
        {"category": "work"},
        {"content": {"contains": "meeting"}}
    ]
}

# Less efficient: Complex operations first
avoid_filters = {
    "user_id": "alice",
    "AND": [
        {"description": {"icontains": "complex text search"}}
    ]
}
```

<Info icon="check">
  When you reorder filters so indexed fields come first (`good_filters` example), queries typically return faster than the `avoid_filters` pattern where expensive text searches run before simple checks.
</Info>

Vector store support varies. Confirm operator coverage before shipping:

<AccordionGroup>
  <Accordion title="Qdrant">
    Full comparison, list, and logical support. Handles deeply nested boolean logic efficiently.
  </Accordion>
  <Accordion title="Chroma">
    Equality and basic comparisons only. Limited nesting—break large trees into smaller calls.
  </Accordion>
  <Accordion title="Pinecone">
    Comparisons plus `in`/`nin`. Text operators are constrained; rely on tags where possible.
  </Accordion>
  <Accordion title="Weaviate">
    Full operator coverage with advanced text filters. Best option when you need hybrid text + metadata queries.
  </Accordion>
</AccordionGroup>

<Warning>
  If an operator is unsupported, most stores silently ignore that branch. Add validation before execution so you can fall back to simpler queries instead of returning empty results.
</Warning>

### Migrate from earlier filters

```python
# Before (v0.x) - simple key-value filtering only
results = m.search(
    "query",
    filters={"user_id": "alice", "category": "work", "status": "active"}
)

# After (v1.0.0) - enhanced filtering with operators
results = m.search(
    "query",
    filters={
        "user_id": "alice",
        "AND": [
            {"category": "work"},
            {"status": {"ne": "archived"}},
            {"priority": {"gte": 5}}
        ]
    }
)
```

<Note>
  Existing equality filters continue to work; add new operator branches gradually so agents can adopt richer queries without downtime.
</Note>

---

## See it in action

### Project management filtering

```python
# Find high-priority active tasks
results = m.search(
    "What tasks need attention?",
    filters={
        "user_id": "project_manager",
        "AND": [
            {"project": {"in": ["alpha", ""]}},
            {"priority": {"gte": 8}},
            {"status": {"ne": "completed"}},
            {
                "OR": [
                    {"assignee": "alice"},
                    {"assignee": "bob"}
                ]
            }
        ]
    }
)
```

<Info icon="check">
  Tasks returned should belong to the targeted projects, remain incomplete, and be assigned to one of the listed teammates.
</Info>

### Customer support filtering

```python
# Find recent unresolved tickets
results = m.search(
    "pending support issues",
    filters={
        "agent_id": "support_bot",
        "AND": [
            {"ticket_status": {"ne": "resolved"}},
            {"priority": {"in": ["high", "critical"]}},
            {"created_date": {"gte": "2024-01-01"}},
            {
                "NOT": [
                    {"category": "spam"}
                ]
            }
        ]
    }
)
```

<Tip>
  Pair agent ID filters with ticket-specific metadata so shared support bots return only the tickets they can act on in the current session.
</Tip>

### Content recommendation filtering

```python
# Personalized content filtering
results = m.search(
    "recommend content",
    filters={
        "user_id": "reader123",
        "AND": [
            {
                "OR": [
                    {"genre": {"in": ["sci-fi", "fantasy"]}},
                    {"author": {"contains": "favorite"}}
                ]
            },
            {"rating": {"gte": 4.0}},
            {"read_status": {"ne": "completed"}},
            {"language": "english"}
        ]
    }
)
```

<Info icon="check">
  Confirm personalized feeds show only unread titles that meet the rating and language criteria.
</Info>

### Handle invalid operators

```python
try:
    results = m.search(
        "test query",
        filters={
            "user_id": "alice",
            "invalid_operator": {"unknown": "value"}
        }
    )
except ValueError as e:
    print(f"Filter error: {e}")
    results = m.search(
        "test query",
        filters={"user_id": "alice", "category": "general"}
    )
```

<Warning>
  Validate filters before executing searches so you can catch typos or unsupported operators during development instead of at runtime.
</Warning>

---

## Verify the feature is working

- Log the filters sent to your vector store and confirm the response metadata matches every clause.
- Benchmark queries before and after indexing to ensure latency improvements materialize.
- Add analytics or debug logging to track how often fallbacks execute when operators fail validation.

---

## Best practices

1. **Use indexed fields first:** Order filters so equality checks run before complex string operations.
2. **Combine operators intentionally:** Keep logical trees readable—large nests are harder to debug.
3. **Test performance regularly:** Benchmark critical queries with production-like payloads.
4. **Plan graceful degradation:** Provide fallback filters when an operator isn’t available.
5. **Validate syntax early:** Catch malformed filters during development to protect agents at runtime.

---

<CardGroup cols={2}>
  <Card title="Explore Vector Store Options" icon="database" href="/components/vectordbs/overview">
    Compare operator coverage and indexing strategies across supported stores.
  </Card>
  <Card title="Tag and Organize Memories" icon="tag" href="/cookbooks/essentials/tagging-and-organizing-memories">
    Practice building workflows that label and retrieve memories with clear metadata filters.
  </Card>
</CardGroup>
