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

# BigQuery Row-Level Security

Row access policies control which rows of a BigQuery table each aidnn user can see. A policy is a SQL filter attached to a table on the BigQuery connector, and aidnn applies it automatically to every query that touches the table. The result: the same question returns different rows for different users. Policies are stored in aidnn, not in BigQuery — nothing changes on the BigQuery side.

## How it works

* **Entitlements live in a BigQuery table.** A table (for example, `user_entitlements`) maps each user's email to the team whose data they can see. A persona column marks the exceptions — users with the **all** persona see every row.
* **The protected table carries a matching column.** The table you want to protect (for example, `fin_table`) has a `team` column. A user sees a row only when their team matches it.
* **aidnn applies the filter on every access.** Whenever the table is queried — from a notebook or a published dashboard — aidnn rewrites the query with the policy filter.

## Quick walkthrough

The rest of this guide walks through an example with two teams and three users:

| User            | Team    | Persona | Sees                 |
| --------------- | ------- | ------- | -------------------- |
| `hr.user1`      | hr      | –       | RECRUITING rows only |
| `finance.user1` | finance | –       | BILLING rows only    |
| `data.user1`    | data    | all     | every row            |

## 1. Store entitlements as a table in BigQuery

The `user_entitlements` table records this mapping — one row per user. Because entitlements are ordinary rows in a table, admins can update who sees what with a quick `INSERT` or `UPDATE` — the change takes effect on the next query.

<Frame>
  <img src="https://mintcdn.com/isotopesai-e9db0a5b/ICubXXuQyn56AAEB/images/bigquery-rls-user-entitlements.png?fit=max&auto=format&n=ICubXXuQyn56AAEB&q=85&s=567bb6b89c5949b54a07a9f310c9689b" alt="User entitlements table in BigQuery" width="1392" height="1004" data-path="images/bigquery-rls-user-entitlements.png" />
</Frame>

The table to protect, `fin_table`, carries a `team` column alongside the data. Rows tagged finance hold the BILLING product; rows tagged hr hold RECRUITING.

<Frame>
  <img src="https://mintcdn.com/isotopesai-e9db0a5b/ICubXXuQyn56AAEB/images/bigquery-rls-fin-table.png?fit=max&auto=format&n=ICubXXuQyn56AAEB&q=85&s=344e5d1312851dd679bdc58cfc00560f" alt="Finance table in BigQuery with a team column" width="1922" height="1298" data-path="images/bigquery-rls-fin-table.png" />
</Frame>

## 2. Add a row access policy

Admins define policies on the connector's detail page. Open the BigQuery connector from the **Connectors** page, find the **Row Access Policies** section, and click **Add policy**. Pick the table to protect and enter the filter as SQL:

```sql theme={null}
SELECT *
FROM fin_table
WHERE EXISTS (
  SELECT 1
  FROM user_entitlements e
  WHERE LOWER(e.email) = LOWER('@viewer_email')
    AND (e.persona = 'all' OR e.team = fin_table.team)
)
```

This policy lets a user see a row when they have the **all** persona or belong to the row's team. At query time, `'@viewer_email'` becomes the signed-in user's email.

<Frame>
  <img src="https://mintcdn.com/isotopesai-e9db0a5b/ICubXXuQyn56AAEB/images/bigquery-rls-add-policy.png?fit=max&auto=format&n=ICubXXuQyn56AAEB&q=85&s=89e18f557c6d71dcb323a34851f24042" alt="Row Access Policies section on the BigQuery connector detail page" width="2374" height="1736" data-path="images/bigquery-rls-add-policy.png" />
</Frame>

Two things to note:

* The policy is stored in aidnn, not in BigQuery — no BigQuery row access policies or IAM changes are needed.
* aidnn applies the filter whenever the table is accessed.

## 3. Ask the same question as different users

### A team member sees only their team's rows

`hr.user1`, who belongs to the hr team, asks *Show total and average ARR by quarter by product* in a notebook. The policy filters `fin_table` down to hr's rows, so the answer covers only the RECRUITING product — 22 quarter-product rows.

<Frame>
  <img src="https://mintcdn.com/isotopesai-e9db0a5b/ICubXXuQyn56AAEB/images/bigquery-rls-hr-notebook.png?fit=max&auto=format&n=ICubXXuQyn56AAEB&q=85&s=298949536d43e79c7a2aba5ce76ae69f" alt="hr.user1's notebook returns only RECRUITING rows" width="3288" height="1750" data-path="images/bigquery-rls-hr-notebook.png" />
</Frame>

### An "all" persona user sees everything

`data.user1`, who has the **all** persona and can see every row, asks the same question. Nothing is filtered out — the answer covers both BILLING and RECRUITING, 55 rows.

<Frame>
  <img src="https://mintcdn.com/isotopesai-e9db0a5b/ICubXXuQyn56AAEB/images/bigquery-rls-data-user-notebook.png?fit=max&auto=format&n=ICubXXuQyn56AAEB&q=85&s=c90eb904e17f09786411e3d16574c934" alt="data.user1's notebook returns all rows" width="2282" height="1736" data-path="images/bigquery-rls-data-user-notebook.png" />
</Frame>

## 4. Share a dashboard

Row access policies follow the data into published dashboards. The filter always runs against the viewer's entitlements — not the author's — so one shared dashboard shows each user a different slice.

### Step 1 - Create and publish the dashboard

`data.user1` asks aidnn to *create a dashboard out of this*. aidnn builds the **Quarterly ARR dashboard** from the full 55-row result, and `data.user1` publishes it.

<Frame>
  <img src="https://mintcdn.com/isotopesai-e9db0a5b/ICubXXuQyn56AAEB/images/bigquery-rls-create-dashboard.png?fit=max&auto=format&n=ICubXXuQyn56AAEB&q=85&s=3365859ad9d81c3d77840346bb2f8361" alt="data.user1 creates the Quarterly ARR dashboard" width="2420" height="1730" data-path="images/bigquery-rls-create-dashboard.png" />
</Frame>

Viewing it, `data.user1` sees both products — all 55 rows.

<Frame>
  <img src="https://mintcdn.com/isotopesai-e9db0a5b/ICubXXuQyn56AAEB/images/bigquery-rls-data-user-dashboard.png?fit=max&auto=format&n=ICubXXuQyn56AAEB&q=85&s=6ce1f114c2c79eda30b1635166129990" alt="data.user1's view of the dashboard shows all products" width="2666" height="1556" data-path="images/bigquery-rls-data-user-dashboard.png" />
</Frame>

### Step 2 - finance.user1 opens it and sees only finance rows

Published dashboards appear under the **Dashboards** section. `finance.user1` finds `data.user1`'s dashboard under **All Dashboards**.

<Frame>
  <img src="https://mintcdn.com/isotopesai-e9db0a5b/ICubXXuQyn56AAEB/images/bigquery-rls-finance-dashboard-list.png?fit=max&auto=format&n=ICubXXuQyn56AAEB&q=85&s=53539f15c43a8832179eda45180009b9" alt="finance.user1 sees the published dashboard in the Dashboards list" width="1338" height="1496" data-path="images/bigquery-rls-finance-dashboard-list.png" />
</Frame>

Opening it, `finance.user1` sees only the finance team's slice — BILLING rows, 33 of the 55.

<Frame>
  <img src="https://mintcdn.com/isotopesai-e9db0a5b/ICubXXuQyn56AAEB/images/bigquery-rls-finance-dashboard-view.png?fit=max&auto=format&n=ICubXXuQyn56AAEB&q=85&s=c72566b6c04bf1686a1a0512a0938230" alt="finance.user1's view of the dashboard shows only BILLING rows" width="1854" height="1488" data-path="images/bigquery-rls-finance-dashboard-view.png" />
</Frame>

### Step 3 - hr.user1 opens it and sees only hr rows

`hr.user1` finds the same dashboard under the **Dashboards** section.

<Frame>
  <img src="https://mintcdn.com/isotopesai-e9db0a5b/ICubXXuQyn56AAEB/images/bigquery-rls-hr-dashboard-list.png?fit=max&auto=format&n=ICubXXuQyn56AAEB&q=85&s=53bedce6cb6e33fc430745dfde675191" alt="hr.user1 sees the published dashboard in the Dashboards list" width="1652" height="1746" data-path="images/bigquery-rls-hr-dashboard-list.png" />
</Frame>

Opening it, `hr.user1` sees only RECRUITING rows — 22 of the 55.

<Frame>
  <img src="https://mintcdn.com/isotopesai-e9db0a5b/ICubXXuQyn56AAEB/images/bigquery-rls-hr-dashboard-view.png?fit=max&auto=format&n=ICubXXuQyn56AAEB&q=85&s=7e740fb8b8f4bc915a15058b7bc46163" alt="hr.user1's view of the dashboard shows only RECRUITING rows" width="2122" height="1732" data-path="images/bigquery-rls-hr-dashboard-view.png" />
</Frame>

One dashboard, three users, three different views — each filtered by the row access policy against the viewer's own entitlements.

## Support

Please reach out to [support@isotopes.ai](mailto:support@isotopes.ai) for more details if you face any issues.
