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

# Row-level security

> RLS narrows what's inside a shared database or entity. A member who can read the Customers table can still be restricted to a subset of rows.

**Row-level security** (RLS) is a per-member filter applied on top of a database or entity the member already has access to. It answers the question "of the rows in this table, which ones is this person allowed to see?"

## When to use it

Resource [permissions](/docs/permissions/overview) decide whether a member can read a dataset at all. RLS decides which **rows** inside it they see. Typical reasons:

* A salesperson should see only their own accounts in `customers`.
* A regional manager should see only invoices for their region.
* Each site's staff should see only their site's rows—one policy, filtered per person.

If a member shouldn't see the table at all, revoke the resource grant—don't try to enforce that through RLS.

With **no policies, access is unrestricted**: everyone who can read the dataset sees every row. Each policy you add limits which rows its audience sees.

## What a policy says

A **row policy** reads as a sentence:

> **\<audience>** can only see rows where **\<column> \<operator> \<value>**

It has four parts.

### Audience — who the policy applies to

| Audience     | Applies to                                          |
| ------------ | --------------------------------------------------- |
| **Everyone** | Every member who can read the dataset.              |
| **Group**    | Members of a specific [group](/docs/permissions/groups). |
| **User**     | One specific member.                                |

### Column and operator

The policy filters one **column**. Available operators:

| Operator           | Meaning                                   |
| ------------------ | ----------------------------------------- |
| `IN` / `NOT IN`    | Column value is (not) in the allowed set. |
| `=` / `≠`          | Equal / not equal.                        |
| `≥`, `≤`, `>`, `<` | Numeric or date comparisons.              |

### Value source — static or dynamic

This is the part that makes one policy serve a whole team:

| Source              | What it compares against                                                                                                                 |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| **Specific values** | Static values you type (`paris, lyon`). Every member in the audience gets the same filter.                                               |
| **User attribute**  | The member's own [attribute](/docs/permissions/attributes) value. The filter is different for each person—`region IN their Région attribute`. |

A **user-attribute** policy uses equality operators only (`=` or `IN`). And it **fails closed**: a member with no value for that attribute sees **no rows**. This is deliberate—a blank attribute must never mean "see everything."

## Combining policies

A member can be hit by several policies. Two rules decide the result.

**Different columns AND together.** A policy on `site` and a policy on `status` both apply—a row must pass both to be visible.

**Same column: the most specific audience wins.** If more than one policy targets the same column, precedence is **user > group > everyone**. The most specific tier applies and the others are skipped for that column. Example: "everyone sees only their own site" plus "the Directors group sees sites A–D" means a director sees A–D (the group tier wins over everyone), while everyone else sees just their own.

## Where filters are applied

RLS is enforced server-side, before data ever reaches the member:

* **Pipeline and dashboard reads** — applied as a `WHERE` wrapper or an in-memory filter before the table is served.
* **App queries** — the [app's query endpoint](/docs/applications/bindings) reuses the same resolution.
* **Natural-language queries** — the [ontology query](/docs/ontology/query) path applies RLS to the generated SQL.
* **Chat tools** — data-returning [assistant tools](/docs/assistant/tools) resolve the same filters, so the AI can't surface rows you couldn't see.
* **Action writes** — when a viewer writes a row through an [app action](/docs/applications/actions), the platform checks the new row against the member's filters and rejects writes that would land in a row they couldn't read.

## Fail-closed on writes

For **reads**, a row missing an RLS-filtered column is skipped (the platform can't evaluate the filter, so it conservatively excludes the row).

For **writes**, a payload missing an RLS-constrained column is **rejected**—otherwise a viewer could dodge their policy by omitting the column.

## Who bypasses RLS

* **Owners** are never subject to RLS.
* A member with effective **Admin on the dataset** (direct or inherited) is not filtered—they manage it, so they see all of it.
* Everyone else—admins and viewers without Admin on that dataset—is filtered.

## Configure a policy

From the dataset or entity's **Access** panel, open **Row-Level Security** and **Add policy**:

1. Pick the **column** to filter.
2. Choose the **value source**—specific values, or a user attribute.
3. Pick the **operator** and enter the values (or select the attribute).
4. Choose the **audience**—everyone, a group, or a member.
5. Save. The policy takes effect on the next query.

Test by impersonating the member or using a viewer account—the filtered table should show only the matching rows.
