> For the complete documentation index, see [llms.txt](https://docs.telys.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.telys.ai/understand-telys/metadata-and-filtering.md).

# Metadata & filtering

Every record can carry a **metadata** dictionary — a small payload of structured facts about the text. Return it with results, or *filter* on it so a search considers only matching records. Filtering is deliberately small: the only filter is `Eq(column, value)`, an exact equality on one declared column. This page shows how to attach metadata, declare filterable keys, filter searches and `ids()`, and model your data around single-equality.

## Attach metadata on write

Metadata is a list of dicts aligned position-by-position with your ids. Pass it to any ingest call:

```python
from telys import Telys
from telys.embedding import AlgentaMultigramEmbedder

db = Telys("./memory")
emb = AlgentaMultigramEmbedder()

col = db.create_collection(
    "docs",
    dim=emb.profile.dimension,
    partition_by="scope",
    embedder=emb,
    filter_columns=("lang", "kind"),     # only these keys become filterable
)

col.add_texts(
    texts=["def add(a, b): return a + b", "SELECT * FROM users"],
    ids=["py-1", "sql-1"],
    metadata=[
        {"lang": "python", "kind": "code"},
        {"lang": "sql", "kind": "code"},
    ],
)
col.save()
```

Only the keys you declare in `filter_columns` are stored as retrievable columns. Whether a key can be filtered — and whether it returns from `with_metadata=True` — depends on that declaration.

## Declare filter\_columns at creation

`filter_columns` is a tuple passed to `create_collection` naming the metadata keys Telys stores as indexed, retrievable columns. Undeclared keys are **not** stored — they cannot be filtered and do not return from `with_metadata=True`. Declare a field to read it back or filter on it (list the partition key too if you want it in the `with_metadata` payload).

```python
col = db.create_collection(
    "docs",
    dim=emb.profile.dimension,
    partition_by="scope",
    embedder=emb,
    filter_columns=("lang", "kind"),     # declare up front
)
```

{% hint style="warning" %}
Declare filter columns **before** you add data. A `where=Eq(...)` should reference a declared column; filtering on an undeclared key has no index to use. Decide your filter axes when you design the collection.
{% endhint %}

## Filter with Eq — the only filter

`Eq(column, value)` is the sole filter class in Telys. Pass it as `where=` on a search, or use the equivalent single-key dict:

```python
from telys import Eq

res = col.search_text(
    "add two numbers",
    top_k=5,
    where=Eq("lang", "python"),          # only records with lang == "python"
    with_metadata=True,
)
print(res["ids"])
print(res["metadata"])

# The single-key dict form is equivalent:
res = col.search_text("add two numbers", where={"lang": "python"})
```

There are **no** `And` / `Or` / `Not` operators, no inequality (`Ne`), no ranges (`Gt` / `Lt`), and no `In`. A filter is exactly one column equal to exactly one value. That is the entire filter surface — see [Filters (Eq)](/reference/filters.md).

## Return payloads with with\_metadata

By default, queries return only ids and scores. Set `with_metadata=True` to also get each hit's stored metadata dict, aligned to `ids`:

```python
res = col.search_text("add two numbers", top_k=5, with_metadata=True)
for _id, meta in zip(res["ids"], res["metadata"]):
    print(_id, meta)
```

## Scope ids() with a filter

`ids()` lists the live external ids and accepts the same filter, so you can enumerate one facet without running a search:

```python
python_ids = col.ids(where=Eq("lang", "python"))
all_ids = col.ids()                      # every live id
```

## Modeling around single-equality

One equality per query sounds restrictive, but two features cover most needs:

* **Use `partition_by` for your primary axis.** The partition key (tenant, user, project) is a physical split, not a filter — queries are naturally scoped to it. Compose a multi-part key with `scope_key(...)`; a list/tuple passed to `partition_by` uses only its first element. See [Partitions & scope keys](/understand-telys/partitions-scope-key.md).
* **Use one `Eq` for a secondary facet.** With the primary axis handled by partitioning, a single equality (e.g. `lang == "python"`) usually covers the remaining cut.

For two combined facets, precompute a composite metadata value at write time — store `{"lang_kind": "python:code"}` and filter `Eq("lang_kind", "python:code")` — rather than reaching for a boolean combinator that does not exist.

| Supported                            | Not supported                   |
| ------------------------------------ | ------------------------------- |
| `Eq(column, value)`                  | `And` / `Or` / `Not`            |
| `where={column: value}` (single key) | `Ne`, `Gt`, `Lt`, `In`, ranges  |
| `with_metadata=True` payloads        | multi-key dict filters          |
| `ids(where=...)`                     | filtering on undeclared columns |

{% hint style="info" %}
Over MCP the filter is a single-key equality object; a multi-key `where` raises an error. The five MCP tools expose search with a single-key `where`, and no update, delete, or tune tools. See [MCP overview](/mcp/overview.md).
{% endhint %}

## Next steps

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Filter results</strong></td><td>A hands-on guide to filtering searches with Eq.</td><td><a href="/pages/f1kH8mnLEAKBG7sEkDOZ">/pages/f1kH8mnLEAKBG7sEkDOZ</a></td></tr><tr><td><strong>Filters (Eq)</strong></td><td>The complete Eq reference and its exact semantics.</td><td><a href="/pages/Gecj6sBExuNdfnjm5iOB">/pages/Gecj6sBExuNdfnjm5iOB</a></td></tr><tr><td><strong>List &#x26; fetch by ID</strong></td><td>Enumerate ids, optionally scoped by an Eq filter.</td><td><a href="/pages/4W3Y4FFEy7tpVma7Bhvc">/pages/4W3Y4FFEy7tpVma7Bhvc</a></td></tr></tbody></table>

Next: read [Records, IDs & payloads](/understand-telys/records-ids-payloads.md) to see how metadata rides alongside vectors.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.telys.ai/understand-telys/metadata-and-filtering.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
