Skip to content

React SDK

@concepttocloud/saiku-embed-react is a typed React wrapper around the existing <saiku-embed> custom element. The element already works in React — React 18+ forwards unknown attributes straight through to the DOM — but React teams evaluate against npm install + typed imports, not <script> tags. This SDK closes that gap without changing the underlying runtime.

Ships in saiku v4.7 as saiku#1432.

Install

Terminal window
npm install @concepttocloud/saiku-embed-react react

The package peer-depends on React 18+. The base @concepttocloud/saiku-embed is a runtime dependency and gets pulled in automatically.

Use

import { SaikuEmbed } from "@concepttocloud/saiku-embed-react";
function Dashboard({ token }: { token: string }) {
return (
<SaikuEmbed
server="https://YOUR-WORKSPACE.saiku.bi"
token={token}
path="homes/admin/Sales.saiku"
render="chart"
mode="bar"
height="480px"
/>
);
}

Importing the package has the side effect of registering the underlying custom element — you don’t need a separate import "@concepttocloud/saiku-embed" unless you also want the tag available outside of the React tree.

Props

PropTypeDefaultNotes
serverstringoptionalOrigin of the Saiku launcher. Omit for same-origin embeds.
pathstringrequiredQuery path / dashboard path / cube ref depending on kind.
kind"query" | "dashboard" | "ai""query"Selects the embed flavour.
tokenstringoptionalEmbed token minted server-side. Omit for public grants.
render"table" | "matrix" | "chart""table"Only meaningful for kind="query".
mode"bar" | "line" | "pie""bar"Only meaningful for render="chart".
heightstring"400px"CSS height of the rendered surface.
styleReact.CSSPropertiesoptionalStandard React style prop.
classNamestringoptionalStandard React className prop.
idstringoptionalPassed through for e2e selectors.
data-testidstringoptionalPassed through to the DOM node.

Full type declarations ship in the package’s index.d.ts.

Kinds

Saved query as a table

<SaikuEmbed
server="https://saiku.example.com"
token={token}
path="homes/admin/Sales.saiku"
height="400px"
/>

Chart

<SaikuEmbed
server="https://saiku.example.com"
token={token}
path="homes/admin/Sales.saiku"
render="chart"
mode="bar"
height="500px"
/>

Matrix

Matrix mode preserves the row / column axis structure — measures on columns, dimension members on rows — instead of flattening to a single row-key map like render="table" does. Useful for pivot-style reports.

<SaikuEmbed
server="https://saiku.example.com"
token={token}
path="homes/admin/Sales.saiku"
render="matrix"
height="500px"
/>

AI ask widget

Point the token at a cube (rather than a saved query) and drop in a plain-English ask box. Requires an AI-kind embed token and a launcher with an LLM provider configured.

<SaikuEmbed
server="https://saiku.example.com"
token={aiToken}
kind="ai"
path="foodmart/FoodMart/FoodMart/Sales"
height="240px"
/>

Saved dashboard

<SaikuEmbed
server="https://saiku.example.com"
token={token}
kind="dashboard"
path="homes/admin/exec.saikudash"
height="700px"
/>

Anonymous public embed

If the resource is marked publicly embeddable on the server (see Public embeds), omit the token entirely:

<SaikuEmbed
server="https://saiku.example.com"
path="shared/public-chart.saiku"
render="chart"
/>

Minting a token from your server

mintEmbedToken() is a Node / edge-function helper for the very common case of minting an embed token on behalf of an end user before rendering <SaikuEmbed>.

app/api/embed-token/route.ts
import { mintEmbedToken } from "@concepttocloud/saiku-embed-react";
export async function POST(request: Request) {
const auth = "Basic " + Buffer.from(
`${process.env.SAIKU_USER}:${process.env.SAIKU_PASS}`,
).toString("base64");
const { token, expiresAt } = await mintEmbedToken({
server: process.env.SAIKU_URL!,
authorization: auth,
resourceKind: "query",
resourcePath: "homes/admin/Sales.saiku",
ttlHours: 24,
label: "Public marketing page",
});
return Response.json({ token, expiresAt });
}

Client-side the token lands in the token prop:

"use client";
import useSWR from "swr";
import { SaikuEmbed } from "@concepttocloud/saiku-embed-react";
export default function EmbedTile() {
const { data } = useSWR("/api/embed-token", (u) => fetch(u, { method: "POST" }).then((r) => r.json()));
if (!data?.token) return <div>Loading…</div>;
return <SaikuEmbed server="" token={data.token} path="homes/admin/Sales.saiku" />;
}

mintEmbedToken options

OptionTypeNotes
serverstringBase URL of the launcher.
authorizationstringValue for the Authorization header (Basic … or Bearer …).
resourceKind"query" | "dashboard" | "ai"Kind of resource the token pins.
resourcePathstringPath (for query/dashboard) or cube ref (for ai).
ttlHoursnumber (optional)Token lifetime; server default is 72h.
labelstring (optional)Human label the admin UI shows next to the token.
fetchtypeof fetch (optional)Overridable for tests + non-browser runtimes (e.g. Cloudflare Workers).

Returns { token, expiresAt }. Throws if the server returns non-2xx or the response body isn’t a token envelope.

Autocomplete on the raw tag

The package augments both the global JSX.IntrinsicElements (React 17/18) and React.JSX.IntrinsicElements (React 19+) so the raw custom element gets the same typed prop set as <SaikuEmbed>. Use whichever you prefer:

// Typed React component
<SaikuEmbed server="" token={token} path="" render="chart" />
// Or the raw custom element (also typed)
<saiku-embed server="" token={token} path="" render="chart" />

Theming

The embed lives inside a shadow root, so host page CSS can’t leak in. Recolour via CSS custom properties on the wrapper:

<SaikuEmbed
server=""
token={token}
path=""
style={{
"--saiku-embed-fg": "#0f172a",
"--saiku-embed-bg": "transparent",
"--saiku-embed-border": "#cbd5e1",
"--saiku-embed-header-bg": "#f1f5f9",
"--saiku-embed-tile-bg": "#ffffff",
"--saiku-embed-row-hover": "#e2e8f0",
"--saiku-embed-negative": "#b91c1c",
} as React.CSSProperties}
/>

Full list of themable variables is on the base embed page.

Version pinning

The React SDK’s version tracks the base @concepttocloud/saiku-embed release one-to-one. 3.19.0 of the SDK uses 3.19.0 of the base runtime — the runtime dep is pinned at release time so a consumer can never accidentally mix majors across the two.

Bundle size

  • Wrapper: ~1 KB gzipped (the whole runtime is a single React.createElement call).
  • Base custom element: ~213 KB gzipped (Svelte 5 CE runtime + ECharts + the embed renderers).
  • React: peer dep — not counted against either package.

Non-goals for v1

  • React hooks for query results (useSaikuQuery) — the custom element handles its own state; a typed data hook duplicates the AI Query API surface. Follow-up if someone asks.
  • Server-component variant<SaikuEmbed> is browser-only (it renders a shadow root). Server-rendered initial data for hydration is a follow-up if the pattern proves painful.
  • Per-kind bundle splitting — the current wrapper always pulls the full base bundle. Tree-shaking by kind is a base-package change, not a wrapper change.

Where to go next