This is the single reference for how DataBrain enforces tenant isolation and metric visibility when you embed dashboards in a multi-tenant application.
Concepts, top-down
- Workspace — outermost container. Holds Data Sources, Datamarts, Data Apps, Dashboards, Metrics. Members and permissions are Workspace-scoped. Most teams run one Workspace.
- Data Source — connection to your warehouse. Credentials live here.
- Datamart — logical selection of tables, columns, relationships, and joins on top of a Data Source. This is where the Client ID column for tenant isolation is set, and where the semantic layer lives.
- Data App — the unit of embedding. Carries the API key that authenticates guest-token minting and the reference to its Datamart. One Data App per embedding surface — you do not need a Data App per tenant.
- Client Group — a logical grouping of end-tenants that share a dashboard structure.
- Manage Metrics — a per-dashboard toggle that lets users pick which metrics appear. See the gotcha below before relying on it.
The tenant identifier: three names, three places
Tenant-isolation strategies
1. Row-per-tenant (client_id column)
All tenants share tables; every table has a tenant column. Configure the Datamart’s Client ID column mapping. How the filter is applied depends on the metric type:
- Builder (visual/dataset) metrics: DataBrain auto-injects the tenant filter into the query. Do not add your own.
- Custom SQL metrics: the filter is not auto-injected. You must write it yourself:
A custom SQL metric that omits the tenant filter runs unscoped and returns every tenant’s rows. Always include 'client_id_variable' in custom SQL when your Datamart uses table-level tenancy.
2. Schema- or database-per-tenant
Each tenant has its own schema/database. Reference it in metric SQL via {{DATABASE_NAME}}, which resolves from the guest token’s clientId through the Datamart mapping.
Anti-pattern: hard-coded tenant IDs
Never save a literal tenant id in metric SQL (WHERE client_id = 'acme') — for example by saving a metric while previewing as a specific client. That literal ships to every viewer and has caused real cross-tenant data leaks. If a metric must be scoped to one tenant on purpose, use RLS or an App Filter in the guest token instead.
Manage Metrics scope — the important gotcha
Manage Metrics visibility is per-dashboard, not per-viewer. If Customer A unchecks a metric on a shared dashboard, it disappears for Customer B too. That is by design — do not use Manage Metrics for per-tenant metric visibility. Use the pattern below instead.
Master + client dashboards pattern
For “each customer picks their own metric subset from a shared library”:
- Create one master dashboard with every metric in the library.
- For each customer (or Client Group), create a client dashboard derived from the master — its own layout and visible-metric subset.
- Embed the client dashboard per tenant with that tenant’s
clientId in the guest token.
Client dashboards can be provisioned programmatically — via the Import Dashboard API or the MCP server’s create_embed tool (which can clone a templateDashboardId per client). Note that import/clone is a copy operation: later edits to the master do not propagate to already-created client dashboards.
Row-level security (RLS)
RLS (params.rlsSettings in the guest token) is an additional per-session constraint on top of tenant isolation — for example region = 'NA' for one user. It is enforced server-side and invisible to the end user. Do not use RLS as a substitute for the Datamart’s Client ID mapping.
Client selector
Embedded dashboards never render a client selector — the tenant is fixed by the guest token’s clientId. The client/database dropdown you see inside the DataBrain app is for workspace members previewing data as different clients; it is not part of the embed.
Where each setting lives