Static dashboards are predictable. Engineers control every query, every filter, every piece of data that shows up on screen.
Self-serve analytics in multi-tenant SaaS changes that equation entirely. Users pick their own dimensions, create their own filters, combine data in ways you didn't plan for. One mistake, one missed filter, one poorly scoped query, and Customer A sees Customer B's data. That's not a bug report. That's a breach.
This is why secure self-serve dashboards require row-level security for embedded analytics not as a feature, but as a foundation.
So teams delay self-serve, lock it down until it's barely useful, or never ship it at all. But the problem isn't self-serve. The problem is how most tools bolt security on after building the dashboard layer.
Why most self-serve analytics implementations fail
Here's the typical pattern: a team builds static dashboards first. Each chart has a hand-written SQL query. Filters are hardcoded. Security logic lives in the query itself, usually a WHERE tenant_id = ? clause copy-pasted across dozens of queries.
It works fine for static dashboards because the queries are predictable. You can audit them, test them, lock them down.
Then the team tries to add self-serve. Users need to create their own charts, combine data models, add their own filters. Suddenly the hand-written queries don't scale. You can't predict what users will request. Security logic has to be duplicated everywhere, and each new chart increases the attack surface.
Teams respond in one of three ways:
1. Duplicate dashboards per tenant. Every customer gets their own copy. This works for five customers. It breaks at fifty. At five hundred it's unmanageable.
2. Add "safe mode" restrictions. Users can filter and sort, but they can't add new dimensions or create custom views. This defeats the purpose of self-serve.
3. Delay or abandon self-serve. The risk feels too high, so the feature never ships.
The root cause is the same in all three cases: security is treated as a dashboard concern, not a data concern.
The real problem: security is treated as a dashboard concern
In most BI tools, dashboards decide what data is accessible. Permissions live in queries, or in UI logic that controls which charts a user can see. Security is layered on top of the visualization layer.
This works until you let users create their own visualizations. Now security logic has to be duplicated across every user-generated chart. There's no single source of truth. Every new self-serve feature increases the number of places where a security rule can be missed or implemented incorrectly.
You end up auditing individual dashboards instead of auditing the system. That doesn't scale, and it doesn't inspire confidence.
The architectural shift: security belongs in the data model
The solution is to invert the model. Security shouldn't live in dashboards. It should live in the data layer, before any query is generated.
Here's how that works in practice:
Data models become the single source of truth. Instead of writing SQL queries by hand for each chart, you define reusable data models. These models describe your tables, define your metrics, and enforce your security rules.
Row-level security is defined once, in the model. You write the security logic in one place—the data layer—and it applies everywhere. Every chart, every filter, every user-created dashboard automatically inherits that security logic.
Charts request dimensions and measures, not raw SQL. Whether it's a static chart you built or a self-serve chart a user created, the request is the same: "Give me revenue by region." The data modeling layer generates the SQL, applying security rules automatically.
One security model supports unlimited dashboards. You don't audit dashboards. You audit the data model. If the model is secure, every dashboard built on top of it is secure.
This is how Embeddable's data layer works. Unlike tools that secure dashboards, Embeddable secures the data itself (using Cubejs behind the scenes)—enforcing security at the model layer, not the dashboard layer.
The critical insight: no separate security for self-serve
Here's what separates secure self-serve from risky implementations: static and self-serve dashboards must use the exact same security implementation.
If you have one security model for static dashboards and a different "locked-down" model for self-serve, you've created two systems to maintain, doubled your attack surface, and made self-serve less useful.
The right pattern: both generate data requests in the same format. The data modeling layer handles security identically for both. No special logic. No "safe mode." No separate permissions layer.
If your static dashboards are secure, your self-serve dashboards are secure.
How Embeddable implements this safely
Embeddable's security model uses two primitives: data models and security contexts.
Your server requests a security token from Embeddable's Tokens API and includes a security context—a JSON object describing who the user is:
{
"orgId": "customer-abc",
"userId": 1234
}
This context is passed into every query. You write row-level security rules directly in the model:
SELECT * FROM orders
WHERE org_id = '{ securityContext.orgId }'
AND user_id = { securityContext.userId }
The security context is set server-side by your application—end users never see or control these values.
You can also pass row-level security filters directly in the token request using a filters parameter, and have them enforced on every query. This speeds up development because you don't need to re-audit each data model as it evolves, and any query that can't correctly apply row-level security will simply fail. The result is an even more centralized way to enforce security rules.
Importantly, both static and self-serve dashboards use the same request format. Whether you build a chart or a user creates one in Custom Canvas, the request looks identical:
{
"dimensions": ["orders.region"],
"measures": ["orders.revenue"]
}
The data modeling layer generates SQL with security rules applied automatically. No separate "self-serve mode." No additional security check. Same code path, same security logic.
If one is secure, both are secure.
What end users can and cannot do in self-serve
So what can users actually do in a self-serve environment like Custom Canvas (the name of Embeddable's self-serve feature)?
Users can:
- Use any dimension or measure you've explicitly exposed to them in the data model
- Add filters based on those dimensions
- Combine multiple data models (if you've defined joins)
- Save and share dashboards they create
- Arrange charts in layouts that work for them
Users cannot:
- Write raw SQL
- Access dimensions or measures you haven't declared
- Bypass row-level security rules
- See data outside their security context
- Modify the underlying data models
The boundaries are defined by you, the engineer. You decide which dimensions and measures to expose. You define the security rules in the data model. Users explore freely within those boundaries, but they can't step outside them.
This does mean slightly more upfront work. You can't skip the data modeling step. But the upside is clear and significant: a manageable, scalable approach that works for all your dashboards (customer built or otherwise) and all your customers / tenants.
This is controlled freedom. Users get the power to answer their own questions, but security is guaranteed by design.
If you're in the planning or build phase, see our best-practices guide for self-serve dashboards.
Single-tenant and hybrid architectures work the same way
Some customers demand their own dedicated database. That's database-level security, not just row-level security.
Embeddable handles this through the environment parameter in the token request. When you request a security token, you can specify which database connection to use:
{
"embeddableId": "dashboard-123",
"securityContext": { "orgId": "customer-xyz" },
"environment": "customer-xyz-db"
}
Same dashboards. Same data models. Different database. All configured programmatically, so it works for one and for all.
You can even run a hybrid architecture where some customers share a multi-tenant database and others have dedicated instances. Embeddable doesn't care. The security model works the same way in both cases.
This is covered in more detail in Embeddable's multi-tenant architecture guide.
How teams validate security before going live
During development, you need to test that your security rules actually work. You need to be able to say, "Show me what Customer A sees vs. what Customer B sees."
Embeddable supports this through preset security contexts in the no-code builder. You define a few test contexts (e.g., "Customer A", "Customer B", "Admin"), and you can switch between them instantly while building dashboards.
You can also test your data models in the Data Playground before you use them in any dashboards. You pick a security context, select some dimensions and measures, and see the exact SQL that gets generated and the data that comes back.
This lets you catch security leaks before they hit production. If Customer A's preset shows Customer B's data, you know there's a problem in the data model, not in some dashboard buried three layers deep.
Why this makes self-serve viable in real products
Most teams delay self-serve because they don't trust their security implementation. Every new chart feels like a new risk. Every user-generated dashboard needs manual review.
When security is enforced at the data model layer, that fear goes away. You're not auditing dashboards anymore. You're auditing the data models, and there are far fewer of those.
If the model is secure, every dashboard built on it is secure. Static or self-serve, it doesn't matter.
This is why self-serve analytics becomes viable. Engineers trust the system enough to enable it. Product teams can ship self-serve without waiting for a six-month security audit. Users get the flexibility they need without the company taking on unmanageable risk.
Self-serve becomes the default, not a risk to be mitigated.
FAQ: Questions teams actually ask
Can users craft filters to access other tenants' data?
No. Filters are applied after row-level security. A user can filter by date, product, region—whatever dimensions you've exposed—but the security context is applied first. The SQL always includes WHERE org_id = '{ securityContext.orgId }' before any user-defined filters are added.
What happens if a dashboard URL is shared?
The security token is tied to a specific user session. When a different user loads that URL, your server generates a new token with their security context. They see their own data, not the original user's data.
If you're using Custom Canvas, saved dashboard layouts are stored per customCanvasState key (typically user-123-dashboard-7). The layout is shared, but the data is not.
Does caching introduce security risks?
Caching in Embeddable is scoped to the security context. Two users with different orgId values don't share cached results. The cache key includes the security context as part of its signature, so there's no risk of cross-tenant cache pollution.
How do you audit data access?
Embeddable logs every query with the associated security context and user identifier. You can see who accessed what data and when. This is often a compliance requirement, especially in regulated industries.
What happens when new dimensions are added?
When you add a new dimension to a data model and push it to production, it's immediately available for your team members to use to build static dashboards. You can make it available in self-serve dashboards too by explicitly exposing it (it's hidden by default). The security rules still apply—the new dimension is just another field that gets filtered by the same securityContext logic.
How Embeddable handles self-serve security: summary
The architecture is straightforward:
- Security is enforced at the model layer - row-level security rules live in reusable data models, not in dashboards or queries
- One code path for static and self-serve - both request dimensions and measures; the data modeling layer generates SQL identically for both
- RLS applied before user filters - security context is injected server-side and applied before any user-defined filters or dimensions
- Users never control security context - your application sets
orgId,userId, etc.; end users only select from exposed dimensions and measures - Same architecture for single-tenant and multi-tenant - the
environmentparameter switches databases; security logic stays the same
The takeaway
Self-serve analytics in multi-tenant SaaS isn't risky if you build the architecture correctly from the start.
Security belongs in the data model, not in the dashboard. Static and self-serve must share the same code path. And row-level security should be enforced centrally, automatically, before any SQL is generated.
When those principles are in place, self-serve stops being a security concern and becomes a product feature you can confidently ship.
Embeddable was built around this model. If you're evaluating embedded analytics tools and self-serve is part of your roadmap, it's worth understanding how your vendor handles multi-tenant security at the architecture level—not just as a dashboard feature.




.jpg)


