Embedded Analytics
Embedded analytics are charts, dashboards, and reports rendered inside a SaaS product so customers can analyze their own data without leaving the app. They are the difference between a SaaS that holds data and a SaaS that helps customers act on it.
What embedded analytics is
Embedded analytics is the practice of rendering charts, dashboards, and reports inside another product’s UI rather than in a standalone BI tool. The end user logs into the host SaaS, navigates to a reports page, and sees their own data laid out as charts, tables, and exports — without ever opening Looker, Tableau, or Mode.
The point is end-user value. SaaS customers do not want to copy CSV exports into a separate analytics app. They want to see how their funnel, their customers, or their team is performing in the place they already do their work.
How it differs from internal analytics
Internal BI tools (Looker, Mode, Metabase) are for the company’s own analysts. Embedded analytics is for customers, and the constraints flip:
- Tenancy isolation: every customer must see only their own data, every query.
- Authentication: typically via signed JWTs that scope the query to the tenant.
- Branding: the visualization must match the host product’s design system, not the BI vendor’s.
- Performance: customers tolerate seconds in BI tools; they tolerate hundreds of milliseconds in their primary SaaS.
A good operator dashboard uses embedded analytics as one of its surfaces, typically for “show me the trend” questions. Customer-facing analytics tabs in B2B SaaS are pure embedded analytics products.
Build versus embed-via-vendor
The two production paths are:
- Embed a vendor. ThoughtSpot Embed, Looker Studio, Cube + Recharts, Sigma Embedded, Apache Superset Embed. Fastest to ship, hardest to customize.
- Build with a charting library. Recharts, ECharts, or D3, fed by your own query layer (often Cube, Hex, or a hand-rolled aggregation API).
The studio’s dashboard development team ships both depending on the engagement. Vendor embeds win when time-to-ship matters more than UI control. Custom builds win when the dashboard is a competitive feature of the SaaS, or when row-level security needs to be tightly integrated with the app’s RBAC model.
Security and tenancy patterns
The hardest part of embedded analytics is keeping tenant data isolated. The standard pattern uses three layers:
- Signed JWTs at the embed boundary, carrying tenant id, user id, and scopes.
- Row-level security in the warehouse or aggregation layer, enforced on every query.
- Audit logging for every query, so suspicious cross-tenant access can be detected.
Other production decisions: cache aggregations behind a per-tenant key, use sandboxed iframes when embedding a vendor view, and version the embed token format so rotation is non-breaking.
Code-shape example
A minimal JWT-issuance + query-time scope check:
import jwt from "jsonwebtoken";
interface EmbedClaims {
tenantId: string;
userId: string;
scopes: string[]; // e.g. ["dashboard.revenue.read"]
exp: number; // 30-minute TTL
}
// Issued at page-render time, baked into the iframe URL or postMessage.
export function issueEmbedToken(
tenantId: string,
userId: string,
scopes: string[],
): string {
return jwt.sign(
{
tenantId,
userId,
scopes,
exp: Math.floor(Date.now() / 1000) + 30 * 60,
} satisfies EmbedClaims,
process.env.EMBED_JWT_SECRET!,
{ algorithm: "HS256" },
);
}
// Verified on every query at the analytics service.
export function verifyEmbedToken(token: string): EmbedClaims {
return jwt.verify(token, process.env.EMBED_JWT_SECRET!, {
algorithms: ["HS256"],
}) as EmbedClaims;
}
// Postgres row-level security, enabled per tenant.
// CREATE POLICY tenant_isolation ON revenue
// USING (tenant_id = current_setting('app.tenant_id')::uuid);
export async function runScopedQuery(
claims: EmbedClaims,
sql: string,
params: unknown[],
): Promise<unknown[]> {
return db.transaction(async (tx) => {
await tx.execute(`SET LOCAL app.tenant_id = '${claims.tenantId}'`);
return tx.query(sql, params);
});
}
The combination of JWT scopes + Postgres RLS makes cross-tenant data leaks structurally impossible, even in the presence of a SQL-injection bug at the query layer.
How we implement it at Dashhold
Patterns we ship on embedded-analytics engagements:
- Cube.js as the semantic layer when the team has a small data engineering bench. Cube handles caching, multi-tenancy, and SQL generation; the front-end uses Recharts for rendering.
- Hand-rolled aggregation API when the team has Postgres expertise. Materialized views per common slice, refreshed on a schedule, served behind a JWT-gated REST or GraphQL surface.
- ECharts for dense charts, Recharts for everyday charts. ECharts wins on data density and tooltip control; Recharts wins on React idiomatic-ness.
- A small chart-spec DSL. Customers (and internal analysts) define a chart by spec, not code. The renderer lays it out. Lets non-engineers extend the dashboard without a deploy.
- Per-tenant cache keys with explicit invalidation. Cache TTL plus a
tenant_data_changedinvalidation event. Stale dashboards damage customer trust faster than slow ones.
Common pitfalls
- Skipping row-level security. Application-only filtering breaks the moment someone writes a SQL query without the right WHERE. RLS at the database is the structural backstop.
- Long-lived embed tokens. Tokens older than 30 minutes leak into browser history, share buttons, and screenshots. Issue short, refresh transparently.
- Letting customers customize chart SQL. They will ship queries that take down the warehouse. Expose a chart-spec DSL or a curated set of measures, never raw SQL.
- Not caching aggregations. A dashboard that re-runs a 100M-row aggregation per page load is a customer-rage button.
- Mixing internal and embedded analytics in one tool. The constraints flip. Internal tools accept seconds of latency; customer-facing does not. Internal tools tolerate vendor branding; customer-facing does not.
- Forgetting the export path. Customers want CSV. Build it on day one, scoped by the same JWT, rate-limited, and logged.
Where embedded analytics fits
Every modern B2B SaaS — CRMs, payment platforms, logistics tools, marketing automation — eventually ships embedded analytics. Customers expect it the same way they expect filters and search. The studio’s dashboard development practice builds embedded-analytics surfaces as part of larger dashboard engagements, typically alongside operator-facing internal views and admin panels.
See also
- Operator dashboard — internal-facing companion to embedded analytics
- Admin panel — admin surface that often hosts embedded analytics
- Internal tool — the broader category these surfaces share