> ## Documentation Index
> Fetch the complete documentation index at: https://mem0-feature-memo-claude-plugin-v1.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Memory Filters

> Query and retrieve memories with powerful filtering capabilities. Filter by users, agents, content, time ranges, and more.

> Memory filters provide a flexible way to query and retrieve specific memories from your memory store. You can filter by users, agents, content categories, time ranges, and combine multiple conditions using logical operators.

## When to use filters

When working with large-scale memory stores, you need precise control over which memories to retrieve. Filters help you:

* **Isolate user data**: Retrieve memories for specific users while maintaining privacy
* **Debug and audit**: Export specific memory subsets for analysis
* **Target content**: Find memories with specific categories or metadata
* **Time-based queries**: Retrieve memories within specific date ranges
* **Performance optimization**: Reduce query complexity by pre-filtering

## Filter structure

Filters use a nested JSON structure with logical operators at the root:

```python theme={null}
# Basic structure
{
    "AND": [  # or "OR", "NOT"
        { "field": "value" },
        { "field": { "operator": "value" } }
    ]
}
```

A single bare condition needs no wrapper:

```python theme={null}
# Works: one condition, no wrapper needed
filters = {"user_id": "user_123"}
```

Sibling top-level keys in a flat object are accepted too: the API implicitly ANDs them, so the flat form and the explicit `AND` form are equivalent:

```python theme={null}
# Works: sibling keys are implicitly ANDed
filters = {
    "user_id": "user_123",
    "categories": {"in": ["finance"]}
}

# Equivalent, explicit form
filters = {
    "AND": [
        {"user_id": "user_123"},
        {"categories": {"in": ["finance"]}}
    ]
}
```

Reach for an explicit `AND`, `OR`, or `NOT` wrapper when you need `OR` or `NOT` semantics, or when you need to nest conditions. What the API does reject is an unrecognized top-level key:

```python theme={null}
# Rejected: unknown top-level key
filters = {"user_id": "user_123", "bogus_key": "z"}
# 400: Top-level key must be a logical operator or an allowed field:
# ['AND', 'OR', 'NOT', 'user_id', 'agent_id', 'app_id', 'run_id',
#  'created_at', 'updated_at', 'timestamp', 'expiration_date', 'text',
#  'categories', 'metadata', 'keywords_search', 'memory_ids', 'keywords']
```

<Callout type="info" icon="cloud" color="#00A8FF">
  Both the hosted Platform API and the self-hosted OSS SDK implicitly AND sibling top-level keys, so the flat and explicit `AND` forms behave the same way on either. The real differences: Platform validates each top-level key against a fixed allow-list and returns a 400 for anything else, and Platform has no `nin` operator. See [Enhanced Metadata Filtering](/open-source/features/metadata-filtering) for the OSS grammar.
</Callout>

## Available fields and operators

### Entity fields

| Field      | Operators             | Example                                |
| ---------- | --------------------- | -------------------------------------- |
| `user_id`  | `eq`, `ne`, `in`, `*` | `{"user_id": "user_123"}`              |
| `agent_id` | `eq`, `ne`, `in`, `*` | `{"agent_id": "*"}`                    |
| `app_id`   | `eq`, `ne`, `in`, `*` | `{"app_id": {"in": ["app1", "app2"]}}` |
| `run_id`   | `eq`, `ne`, `in`, `*` | `{"run_id": "*"}`                      |

### Time fields

| Field             | Operators                                                                                  | Example                                      |
| ----------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------- |
| `created_at`      | `gt`, `gte`, `lt`, `lte`, `ne`, `in`. Explicit `eq` is rejected, pass a bare value instead | `{"created_at": {"gte": "2024-01-01"}}`      |
| `updated_at`      | `gt`, `gte`, `lt`, `lte`, `ne`, `in`. Explicit `eq` is rejected, pass a bare value instead | `{"updated_at": {"lt": "2024-12-31"}}`       |
| `timestamp`       | `gt`, `gte`, `lt`, `lte`, `ne`, `in`. Explicit `eq` is rejected, pass a bare value instead | `{"timestamp": {"gt": "2024-01-01"}}`        |
| `expiration_date` | `gt`, `gte`, `lt`, `lte`, `ne`, `in`. Explicit `eq` is rejected, pass a bare value instead | `{"expiration_date": {"lte": "2026-12-31"}}` |

### Content fields

| Field        | Operators                                                                                                  | Example                                  |
| ------------ | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| `categories` | `in` (takes a list, matches any category in it), `contains` (case-insensitive). `eq` and `ne` are rejected | `{"categories": {"in": ["finance"]}}`    |
| `metadata`   | `eq`, `ne`, `contains`                                                                                     | `{"metadata": {"key": "value"}}`         |
| `keywords`   | `contains` (case-sensitive), `icontains` (case-insensitive)                                                | `{"keywords": {"icontains": "invoice"}}` |

### Special fields

| Field        | Operators | Example                          |
| ------------ | --------- | -------------------------------- |
| `memory_ids` | `in`      | `{"memory_ids": ["id1", "id2"]}` |

<Callout type="warning" icon="exclamation-triangle" color="#F7B731">
  The `*` wildcard matches any non-null value. Records with null values for that field are excluded.
</Callout>

<Callout type="info" icon="keyboard" color="#00A8FF">
  Use operator keywords exactly as shown (`eq`, `ne`, `gte`, etc.). SQL-style symbols such as `>=` or `!=` are rejected by the Platform API.
</Callout>

<Callout type="warning" icon="exclamation-triangle" color="#E74C3C">
  There is no `nin` ("not in") operator on Platform. It is an OSS-only operator; see [Enhanced Metadata Filtering](/open-source/features/metadata-filtering). To exclude a set of values on Platform, wrap an `in` clause in `NOT`:

  ```python theme={null}
  {"NOT": {"categories": {"in": ["spam", "test"]}}}
  ```
</Callout>

## Common filter patterns

Use these ready-made filters to target typical retrieval scenarios without rebuilding logic from scratch.

<AccordionGroup>
  <Accordion title="Single user">
    ```python theme={null}
    # Narrow to one user's memories
    filters = {"AND": [{"user_id": "user_123"}]}
    memories = client.get_all(filters=filters)
    ```
  </Accordion>

  <Accordion title="All users">
    ```python theme={null}
    # Wildcard skips null user_id entries
    filters = {"AND": [{"user_id": "*"}]}
    memories = client.get_all(filters=filters)
    ```
  </Accordion>

  <Accordion title="User across all runs">
    ```python theme={null}
    # Pair a user filter with a run wildcard
    filters = {
        "AND": [
            {"user_id": "user_123"},
            {"run_id": "*"}
        ]
    }
    memories = client.get_all(filters=filters)
    ```
  </Accordion>
</AccordionGroup>

<Callout type="warning" icon="exclamation-triangle" color="#E74C3C">
  Metadata filters only support bare values/`eq`, `contains`, and `ne`. Operators such as `in`, `gt`, or `lt` trigger a `FilterValidationError`. For multi-value checks, wrap multiple equality clauses in `OR`.
</Callout>

```python theme={null}
# Multi-value metadata workaround
filters = {
    "OR": [
        {"metadata": {"type": "semantic"}},
        {"metadata": {"type": "episodic"}}
    ]
}
```

### Content search

Find memories containing specific text, categories, or metadata values.

<AccordionGroup>
  <Accordion title="Text search (keywords)">
    ```python theme={null}
    # Substring match on memory text via get_all
    filters = {
        "AND": [
            {"user_id": "user_123"},
            {"keywords": {"icontains": "invoice"}}
        ]
    }
    memories = client.get_all(filters=filters)
    ```

    Use `contains` for case-sensitive matching and `icontains` for case-insensitive.

    <Callout type="warning" icon="exclamation-triangle" color="#E74C3C">
      A `keywords` filter works on `get_all`, but passing it to `search()` currently returns a `500` ([MEM-5746](https://linear.app/mem0/issue/MEM-5746)). For text relevance during a search, pass the text to the `query` argument instead of filtering on `keywords`.
    </Callout>
  </Accordion>

  <Accordion title="Categories">
    ```python theme={null}
    # Match against category list
    filters = {
        "AND": [
            {"user_id": "user_123"},
            {"categories": {"in": ["finance", "health"]}}
        ]
    }

    # Partial category match
    filters = {
        "AND": [
            {"user_id": "user_123"},
            {"categories": {"contains": "finance"}}
        ]
    }
    ```
  </Accordion>

  <Accordion title="Metadata">
    ```python theme={null}
    # Pin to a metadata attribute
    filters = {
        "AND": [
            {"user_id": "user_123"},
            {"metadata": {"source": "email"}}
        ]
    }
    ```
  </Accordion>
</AccordionGroup>

### Time-based filtering

Retrieve memories within specific date ranges using time operators.

<AccordionGroup>
  <Accordion title="Date range">
    ```python theme={null}
    # Created in January 2024
    filters = {
        "AND": [
            {"user_id": "user_123"},
            {"created_at": {"gte": "2024-01-01T00:00:00Z"}},
            {"created_at": {"lt": "2024-02-01T00:00:00Z"}}
        ]
    }

    # Updated recently
    filters = {
        "AND": [
            {"user_id": "user_123"},
            {"updated_at": {"gte": "2024-12-01T00:00:00Z"}}
        ]
    }
    ```
  </Accordion>
</AccordionGroup>

### Multiple criteria

Combine various filters for complex queries across different dimensions.

<AccordionGroup>
  <Accordion title="Multiple users">
    ```python theme={null}
    # Expand scope to a short user list
    filters = {
        "AND": [
            {"user_id": {"in": ["user_1", "user_2", "user_3"]}}
        ]
    }
    ```
  </Accordion>

  <Accordion title="OR logic">
    ```python theme={null}
    # Return matches on either condition
    filters = {
        "OR": [
            {"user_id": "user_123"},
            {"run_id": "run_456"}
        ]
    }
    ```
  </Accordion>

  <Accordion title="Exclude categories">
    ```python theme={null}
    # Wrap negative logic with NOT
    filters = {
        "AND": [
            {"user_id": "user_123"},
            {"NOT": {
                "categories": {"in": ["spam", "test"]}
            }}
        ]
    }
    ```
  </Accordion>

  <Accordion title="Specific memory IDs">
    ```python theme={null}
    # Fetch a fixed set of memory IDs
    filters = {
        "AND": [
            {"user_id": "user_123"},
            {"memory_ids": ["mem_1", "mem_2", "mem_3"]}
        ]
    }
    ```
  </Accordion>

  <Accordion title="All entities populated">
    ```python theme={null}
    filters = {
        "AND": [
            {"user_id": "user_123"},
            {"run_id": "*"},
            {"app_id": "*"}
        ]
    }
    ```

    Matches records where `user_id` equals `user_123` and `run_id`/`app_id` are both non-null. It does not require other entity fields to be null.
  </Accordion>
</AccordionGroup>

## Advanced examples

Level up foundational patterns with compound filters that coordinate entity scope, tighten time windows, and weave in exclusion rules for high-precision retrievals.

<AccordionGroup>
  <Accordion title="Multi-dimensional filtering">
    ```python theme={null}
    filters = {
        "AND": [
            {"user_id": "user_123"},
            {"categories": {"in": ["finance"]}},
            {"keywords": {"icontains": "invoice"}},
            {"created_at": {"gte": "2024-01-01T00:00:00Z"}},
            {"created_at": {"lt": "2024-04-01T00:00:00Z"}}
        ]
    }
    memories = client.get_all(filters=filters)
    ```
  </Accordion>

  <Accordion title="Entity-specific retrieval">
    ```python theme={null}
    # Query agent scope on its own
    filters = {
        "AND": [
            {"agent_id": "finance_bot"}
        ]
    }

    # Or broaden within that scope using wildcards
    filters = {
        "AND": [
            {"agent_id": "finance_bot"},
            {"run_id": "*"}
        ]
    }
    ```
  </Accordion>

  <Accordion title="Nested NOT/OR logic">
    ```python theme={null}
    # User memories from 2024, excluding spam and test
    filters = {
        "AND": [
            {"user_id": "user_123"},
            {"created_at": {"gte": "2024-01-01T00:00:00Z"}},
            {"NOT": {
                "OR": [
                    {"categories": {"in": ["spam"]}},
                    {"categories": {"in": ["test"]}}
                ]
            }}
        ]
    }
    ```
  </Accordion>
</AccordionGroup>

## Best practices

<Callout type="tip" icon="lightbulb" color="#26A17B">
  The root does not have to be `AND`, `OR`, or `NOT`. A bare filter like `{"user_id": "alice"}` works on its own, and several top-level keys in one flat filter are implicitly ANDed. Wrap conditions in a logical operator when you need OR/NOT semantics or nested grouping.
</Callout>

<Callout type="tip" icon="lightbulb" color="#26A17B">
  Use `"*"` to match any non-null value for a field.
</Callout>

<Callout type="warning" icon="exclamation-triangle" color="#E74C3C">
  Combining `user_id` **and** `agent_id` in the same `AND` clause only returns records that have both values set. Memories created by a normal `client.add` never do: each extracted fact is attributed to its speaker, so it carries `user_id` or `agent_id`, not both. Use `OR` to match either scope. Only [Direct Import](/platform/features/direct-import) (`infer=False`) writes both fields on one record.

  A filter object with both an `AND` key and a sibling `OR` key at the same level silently drops the `OR` branch today. Nest the `OR` inside the `AND` array instead of placing them as siblings.
</Callout>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Missing results with agent_id">
    **Problem**: Filtered by `user_id` but don't see agent memories.

    **Solution**: A `user_id` filter only matches records that have that `user_id` set; it won't surface memories written with only `agent_id`. Use OR to query both scopes:

    ```python theme={null}
    {"OR": [{"user_id": "user_123"}, {"agent_id": "agent_name"}]}
    ```
  </Accordion>

  <Accordion title="ne operator returns too much">
    **Problem**: `ne` comparison pulls in records with null values.

    **Solution**: Pair `ne` with a wildcard guard:

    ```python theme={null}
    {"AND": [{"agent_id": "*"}, {"agent_id": {"ne": "old_agent"}}]}
    ```
  </Accordion>

  <Accordion title="Case-insensitive text match">
    **Solution**: Use `keywords` with `icontains` on `get_all`:

    ```python theme={null}
    {"AND": [{"user_id": "user_123"}, {"keywords": {"icontains": "invoice"}}]}
    ```

    This works on `get_all`. It is not supported on `search()` yet (returns a `500`, [MEM-5746](https://linear.app/mem0/issue/MEM-5746)) - for search, pass the text to the `query` argument instead.
  </Accordion>

  <Accordion title="Date range between two dates">
    **Solution**: Use `gte` for the start and `lt` for the end boundary:

    ```python theme={null}
    {"AND": [
        {"created_at": {"gte": "2024-01-01"}},
        {"created_at": {"lt": "2024-02-01"}}
    ]}
    ```
  </Accordion>

  <Accordion title="Metadata filter not working">
    **Solution**: Match top-level metadata keys exactly:

    ```python theme={null}
    {"metadata": {"source": "email"}}
    ```
  </Accordion>
</AccordionGroup>

## FAQ

<AccordionGroup>
  <Accordion title="Do I need AND/OR/NOT?">
    No. A bare filter like `{"user_id": "u1"}` works on its own, and several top-level keys in one flat filter like `{"user_id": "u1", "agent_id": "a1"}` are implicitly ANDed. Reach for `AND`, `OR`, or `NOT` when you need OR/NOT semantics or nested grouping, not merely to combine conditions.
  </Accordion>

  <Accordion title="What does * match?">
    Any non-null value. Nulls are excluded.
  </Accordion>

  <Accordion title="Why use wildcards?">
    Unspecified fields default to NULL. Use `"*"` to include non-null values.
  </Accordion>

  <Accordion title="Is = required?">
    No. Equality is the default: `{"user_id": "u1"}` works.
  </Accordion>

  <Accordion title="Can I filter nested metadata?">
    Only top-level keys are supported.
  </Accordion>

  <Accordion title="How to search text?">
    For a substring match, use the `keywords` filter with `contains`/`icontains` on `get_all`. For relevance-ranked search, pass the text to the `query` argument on `search()`. Note: `keywords` is not supported inside `search()` filters yet (returns a `500`, [MEM-5746](https://linear.app/mem0/issue/MEM-5746)).
  </Accordion>

  <Accordion title="Can I nest AND/OR?">
    ```python theme={null}
    {
        "AND": [
            {"user_id": "user_123"},
            {"OR": [
                {"categories": {"in": ["finance"]}},
                {"categories": {"in": ["health"]}}
            ]}
        ]
    }
    ```
  </Accordion>
</AccordionGroup>

## Known limitations

* Filters only constrain the fields you mention; unmentioned entity fields are not required to be null. A record carries `user_id` or `agent_id` (never both) unless it came from Direct Import, plus whatever `app_id` and `run_id` were passed.
* Metadata supports only bare/`eq`, `contains`, and `ne` comparisons.
* Wildcards (`"*"` ) match only records where the field is already non-null.
