---
title: "Filter shared dashboards by customer · Agent Wranglers"
url: https://agentwranglers.se/playbook/data-engineering/access-and-bi-tools/dashboard-row-security
id: DEN-31
level: Required
risk: data
updated: 2026-10-22
tokens: 910
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Filter shared dashboards by customer inside the warehouse

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Access and BI tools](https://agentwranglers.se/playbook/data-engineering/access-and-bi-tools.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-22.

- ID: DEN-31
- Level: Required
- Chapter: [Access and BI tools](https://agentwranglers.se/playbook/data-engineering/access-and-bi-tools.md)
- Rule: When customers see dashboards built on shared tables, filter rows by customer in the warehouse or in a signed embed token. Never rely on a filter set in the dashboard alone.
- Check: Open an embedded dashboard as customer A, then change the customer ID to B's in the address and in the data request. No row of B's appears.
- Risk: Data leaked or lost
- How: A dashboard filter can be changed in the address or removed by an edit. Use the warehouse's row access policies, or a view per customer, keyed on a customer ID that your server signs into the embed token. It is [Keep each customer's data apart](https://agentwranglers.se/playbook/data/schema-design/tenant-isolation.md) applied to analytics. Search follows the same idea in [Filter search results by access inside the query](https://agentwranglers.se/playbook/data/search/search-access-filter.md). Give each token a lifetime of 10 minutes or less.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### Python example

```python
import time
import jwt

EMBED_LIFETIME_SECONDS = 600

def embed_token(secret: str, dashboard_id: str, customer_id: str) -> str:
    claims = {"dashboard": dashboard_id, "customer_id": customer_id, "exp": int(time.time()) + EMBED_LIFETIME_SECONDS}
    return jwt.encode(claims, secret, algorithm="HS256")
```

### JavaScript example

```js
import jwt from 'jsonwebtoken';

const EMBED_LIFETIME_SECONDS = 600;

export const embedToken = (secret, dashboardId, customerId) =>
  jwt.sign({ dashboard: dashboardId, customer_id: customerId }, secret, { algorithm: 'HS256', expiresIn: EMBED_LIFETIME_SECONDS });
```

## Sources

- [PostgreSQL Row security policies](https://www.postgresql.org/docs/current/ddl-rowsecurity.html)
- [OWASP Authorization Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html)

## Related rules

- DAT-7 [Keep each customer's data apart](https://agentwranglers.se/playbook/data/schema-design/tenant-isolation.md) in Data and databases
- DAT-61 [Filter search results by access inside the query](https://agentwranglers.se/playbook/data/search/search-access-filter.md) in Data and databases

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/data-engineering/access-and-bi-tools/dashboard-row-security">Agent Wranglers data engineering playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Give each pipeline and BI tool its own narrow account](https://agentwranglers.se/playbook/data-engineering/access-and-bi-tools/tool-service-accounts.md)
- Next: [Limit exports and public links from BI tools](https://agentwranglers.se/playbook/data-engineering/access-and-bi-tools/bi-sharing-limits.md)
- [Access and BI tools](https://agentwranglers.se/playbook/data-engineering/access-and-bi-tools.md)
