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

# Filters

> Saved rules that keep the rows matching their conditions on every read of a dataset — manage them in chat, the dataset settings UI, the CLI, MCP, or REST

# Filters

A **filter** is a saved rule on a dataset that **keeps the rows matching its conditions**
and hides the rest. Once you add one, it applies *everywhere* that dataset is read — row
lists, dashboards, public pages, and the agent's own queries — including counts and
totals, not just the visible rows. The model is simple: **capture everything at write
time, filter on read.** Nothing is ever deleted; the rows a filter excludes are just
hidden from view.

Because a filter keeps the rows that *match*, you **exclude by matching the rows you want
to keep**. The most common use is keeping **test and QA submissions** out of a landing
page's real leads: if the test rows all share a value — a test email like `@example.com`
— you keep the real ones with `email not_contains @example.com`. Using `contains` there
would do the opposite, keeping *only* the test rows. Other uses: keep a single status
(`status` equals `won`), drop an archived one (`status` not\_equals `archived`), or scope
to a window (`created_at` greater than a date).

Filters apply to **file datasets** (uploads, lead/form capture, agent-written tables).
Integration datasets enforce access through their source system, so they don't take
filters.

## How a filter is shaped

Each filter has an optional **name**, and one or more **conditions** combined with
AND. A condition is a `column`, an `operator`, and a `value`:

| Operator       | Meaning                                                                                                                                                                                                                    |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `equals`       | column equals the value                                                                                                                                                                                                    |
| `not_equals`   | column does not equal the value                                                                                                                                                                                            |
| `contains`     | column contains the value (case-insensitive substring)                                                                                                                                                                     |
| `not_contains` | column does **not** contain the value (case-insensitive) — **and rows where the column is empty/NULL are kept**, since a blank isn't a match. This is the operator for exclusion (e.g. `email not_contains @example.com`). |
| `greater_than` | column is greater than the value                                                                                                                                                                                           |
| `less_than`    | column is less than the value                                                                                                                                                                                              |
| `between`      | column is between two values — pass them comma-separated, e.g. `10,100`                                                                                                                                                    |

A filter is **default-on** unless you say otherwise: it applies automatically to every
read. A read can opt out (`apply_default_filters=false` on the query API) to see every
row — handy for a one-off admin or debug check.

## Default vs. named filters

A filter can also be saved **not default-on** (set `is_default=false` when you create
it). A non-default filter never applies on its own — it sits on the dataset as a *named
view* that a specific reader can opt into by name, one request at a time. This is how a
consumer of a **shared** dataset narrows what *they* see without changing the dataset
for everyone else: the data owner defines the filter once, the consumer opts in.

Opting into a named filter is **additive** — it applies *on top of* the dataset's
default filters, narrowing the result. It can never be used to bypass a default (a
default that hides test leads stays applied even when you opt into a named view).

### Opting into a named filter on a read

The `query` and `fetch` read APIs take a `filter_names` list. Each name must match a
saved filter on the dataset; an unknown name is rejected with the available names, so a
typo never silently returns unfiltered data.

<CodeGroup>
  ```bash CLI theme={null}
  # Fetch rows, opting into a saved filter by name (repeat --filter for more than one)
  erdo datasets fetch <dataset-slug> --filter "Won only"

  # Combine with a SQL shape and a row cap
  erdo datasets fetch leads --sql "SELECT * FROM data" --filter "Exclude smoke tests" --limit 500
  ```

  ```bash REST theme={null}
  curl -X POST https://api.erdo.ai/v1/datasets/leads/fetch \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "filter_names": ["Exclude smoke tests"] }'

  # Same field on the SQL query endpoint
  curl -X POST https://api.erdo.ai/v1/datasets/leads/query \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "query": "SELECT * FROM data", "filter_names": ["Exclude smoke tests"] }'
  ```
</CodeGroup>

The MCP tools `erdo_run_query` and `erdo_fetch_dataset_contents` take the same
`filter_names` argument. List a dataset's filter names with
`erdo_list_dataset_filters` (or `erdo datasets filter list`).

## In chat

The easiest way — just ask:

> "Add a filter to the leads dataset to hide submissions from anyone testing — their
> emails all end in `@example.com`."

The agent adds the filter, and you can ask it to list or remove filters too. To see
everything again for a one-off check, ask it to query with filters off.

## In the dataset settings

Open a dataset and find the **Filters** section. **Add filter** opens a form where you
name the filter and add one or more conditions; existing filters are listed with their
conditions and a remove button. Removing a filter stops it hiding rows — it never
deletes data.

## CLI

```bash theme={null}
# List the filters on a dataset
erdo datasets filter list <dataset-slug>

# Exclude test submissions — KEEP the rows whose email does NOT contain @example.com
# (repeat --where for each condition; they AND-combine)
erdo datasets filter add <dataset-slug> \
  --name "Exclude test submissions" \
  --where "email not_contains @example.com"

# Save a NAMED VIEW instead (opt-in, not default-on) with --no-default
erdo datasets filter add leads \
  --name "Won only" \
  --where "status equals won" \
  --no-default

# Multiple conditions, and the 'between' form
erdo datasets filter add leads \
  --where "status not_equals archived" \
  --where "score between 10,100"

# Remove a filter by id (from 'filter list')
erdo datasets filter remove <dataset-slug> <filter-id>
```

Each `--where` is a `<column> <operator> <value>` string. Output is JSON.

## MCP tools

| Tool                                             | Description                                                                                                                                                                       |
| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `erdo_add_dataset_filter`                        | Add a filter to a dataset. Takes `dataset_slug`, optional `name`, `conditions` (`column`/`operator`/`value`), and optional `is_default` (default true; false saves a named view). |
| `erdo_list_dataset_filters`                      | List the filters on a dataset (`dataset_slug`). Returns each filter's id, name, conditions, and whether it is default-on.                                                         |
| `erdo_remove_dataset_filter`                     | Remove a filter by `dataset_slug` and `filter_id`.                                                                                                                                |
| `erdo_run_query` / `erdo_fetch_dataset_contents` | Read rows; pass `filter_names` to opt into named (non-default) filters, additive on top of the defaults.                                                                          |

## REST

| MCP tool                     | REST endpoint                                 | Method |
| ---------------------------- | --------------------------------------------- | ------ |
| `erdo_list_dataset_filters`  | `/v1/datasets/:datasetSlug/filters`           | GET    |
| `erdo_add_dataset_filter`    | `/v1/datasets/:datasetSlug/filters`           | POST   |
| `erdo_remove_dataset_filter` | `/v1/datasets/:datasetSlug/filters/:filterID` | DELETE |

```bash theme={null}
# List filters
curl "https://api.erdo.ai/v1/datasets/leads/filters" \
  -H "Authorization: Bearer YOUR_API_KEY"

# Add a filter (default-on)
curl -X POST https://api.erdo.ai/v1/datasets/leads/filters \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Exclude test submissions",
        "conditions": [
          { "column": "email", "operator": "not_contains", "value": "@example.com" }
        ]
      }'

# Add a named view (opt-in only) with is_default:false
curl -X POST https://api.erdo.ai/v1/datasets/leads/filters \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Won only",
        "is_default": false,
        "conditions": [
          { "column": "status", "operator": "equals", "value": "won" }
        ]
      }'

# Remove a filter
curl -X DELETE https://api.erdo.ai/v1/datasets/leads/filters/<filter-id> \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Adding or removing a filter needs **edit** access to the dataset; listing needs
**view** access.
