How do I show each customer only their own data in one embedded dashboard?

In ThoughtSpot, put a row-level security (RLS) rule on every Table that holds customer data, pass each signed-in user's customer ID in their authentication token, and embed the same dashboard for everyone. Store the customer ID in a formula variable: it carries only that user's value and works with numeric columns, so ThoughtSpot generates leaner SQL than it does from group membership. Don't use runtime filters or parameters for this: ThoughtSpot's docs say they are not security features (runtime filters, runtime parameters).

This approach is particularly useful for multi tenant analytics, where multiple customers access the same analytics application but must remain restricted to their own business data.

Checked against the ThoughtSpot Cloud 26.10.0.cl documentation, October 2026.

Row-level security (RLS) filters which rows each user can query, on the server, before any result reaches the browser. In ThoughtSpot, RLS rules are defined on Tables and apply to every Model, Liveboard, search and Spotter answer built on them, so one embedded dashboard can serve many customers safely.

What should you check when you compare tools?

Tenant isolation must run on the server, everywhere users see data. In ThoughtSpot, the same RLS rules cover dashboards, search results and Spotter, ThoughtSpot's AI analyst: "All existing security rules, including column-level and row-level security, are strictly enforced" (Spotter data handling).

Ask any vendor:

  • Is tenant isolation enforced on the server, or by filters set in the browser?

  • Does one rule cover dashboards, search and AI answers?

  • Is multi-tenancy built in, or set up by hand for each customer?

Why not runtime filters?

They're set in the browser, so they shape what a user sees without securing it. RLS rules are defined on Tables, "automatically extend to all Models, saved answers, and Liveboards based on that Table" (Liveboards are ThoughtSpot's dashboards), and users can't change them (RLS rules). AI agents can't remove them either: ThoughtSpot applies them before it returns any data.

Use them for context instead: a user allowed to see all of Australia opens your app's Victoria page, and the app passes Victoria as a runtime filter. RLS still decides what they can see.

This distinction is important when building customer facing dashboards, because application-level filters can customize the viewing experience while server-side row-level security enforces actual data access permissions.

Which pattern fits your data?

Your data

Pattern

All customers in the same tables, one customer per user

A formula variable for the customer ID (this post)

Access depends on attributes: region, product line, several values

One formula variable per attribute (ABAC guide)

Each customer in its own database or schema

One Org per customer (multi-tenancy guide)

Using a shared embedded dashboard with customer-specific access rules is an effective way to scale embedded BI software without maintaining separate analytics experiences for every customer.

How do I set it up?

1. Create a formula variable for the customer ID. Use the REST API, which doesn't need a default value. You need the Can administer ThoughtSpot privilege, or Can manage variables when role-based access control (RBAC) is on:

POST /api/rest/2.0/template/variables/create
{ "type": "FORMULA_VARIABLE", "name": "customer_var", "is_sensitive": false, "data_type": "VARCHAR" }

If customer_id is a number, set "data_type": "INT64": numeric comparisons run faster than string matching. The Admin page's Variables tab currently requires a default value: use TS_WILDCARD_NONE, so a user without a value sees nothing.

2. Add an RLS rule to every Table that holds per-customer data, including dimension tables such as Customers.

customer_id = ts_var(customer_var)

Only = and != work with ts_var(). It needs ABAC via RLS, on by default from ThoughtSpot Cloud 26.4.0.cl.

3. Create an app group and share the dashboard. Create one app group (app_users here), not shareable, and share the dashboard and its Model read-only with it (multi-tenancy within an Org).

4. Pass the user's customer ID in a custom authentication token, from your backend, when the user is new or the value changes:

POST /api/rest/2.0/auth/token/custom
{
  "username": "[email protected]",
  "secret_key": "<SECRET_KEY>",
  "persist_option": "REPLACE",
  "auto_create": true,
  "display_name": "Jane Doe",
  "email": "[email protected]",
  "groups": [{ "identifier": "app_users" }],
  "validity_time_in_sec": 1800,
  "variable_values": [{ "name": "customer_var", "values": ["cust_acme"] }]
}

Use REPLACE: APPEND keeps old values, so a user who moves keeps their old access. Values persist, so send variable_values only when the value changes: every send rewrites them and adds load on the instance. This endpoint sets groups only when it creates the user, so add existing users to the app group yourself.

Can I use groups instead?

If your users already belong to one group per customer, an RLS rule can read membership: customer_id = ts_groups, or customer_id in ts_groups. Both forms resolve to the same IN condition.

Groups cost more at query time: ts_groups carries every group a user belongs to, including groups that only grant privileges, and compares only strings. For an integer column with numeric group names, ts_groups_int compares numbers and runs faster. Search suggestions do respect ts_groups rules for each user.

How do I check it worked?

  1. In the Model's settings, Disable row level security should be off.

  2. Sign in as a test user who isn't an admin and doesn't have the "Can administer and bypass RLS" privilege. "Row-level security does not apply" to administrators (how RLS works).

  3. Check each customer's user sees only their rows, including in a search on a dimension column alone, such as customer name.

  4. POST /api/rest/2.0/template/variables/search with "response_content": "METADATA_AND_VALUES" shows the value each user holds.

Still failing?

  • Charts are empty. The token's value doesn't exactly match the column value, or the user has no value. With no value, ThoughtSpot doesn't query the database at all.

  • A user kept old access after moving. The token used APPEND. Use REPLACE.

  • Every user sees every customer. Row-level security is disabled on the Model, or a Table is missing its rule.

  • Other customers' names show in search suggestions. ts_var rules don't filter them: turn off indexing for that column.

How do I explain this to my team?

Who

What they'll ask

What you can tell them

Security

Can customer A see customer B's data?

Not if every per-customer Table has an RLS rule and row-level security isn't disabled on the Model. Rules run on the server, and Spotter "only sees what each user is permitted to see".

Engineering lead

Do we build a dashboard per customer?

No. One dashboard for everyone; our token service tells ThoughtSpot which customer each user belongs to.

Proof you can forward: Clerri evaluated seven vendors and chose ThoughtSpot for its "build once, deploy anywhere" model. Embedding it and rolling it out to customers "only took 3 months" (Clerri case study).

How can I try this before I commit?

Start a free ThoughtSpot Embedded trial.

Related