# GoodData.UI Documentation (full text) > GoodData.UI: A React SDK, Web Components, and REST API clients for building analytics into your application. Every guide page of the documentation, in sidebar order, as Markdown. The index of all pages, including the API reference (one Markdown page per package and per symbol), is https://www.gooddata.ai/docs/gooddata-ui/llms.txt. --- # Getting started > GoodData.UI is the official React SDK for embedding GoodData analytics, from single charts and filters to whole dashboards and the AI assistant, directly into your application. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/index.md GoodData.UI talks to [GoodData Cloud and GoodData.CN](https://www.gooddata.ai/) over the analytical backend API and ships as a set of npm packages under the `@gooddata/*` scope. The SDK is written for React. If your app is Angular, Vue, or plain HTML, the same components are available as [Web Components](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/index.md). If you only need a saved dashboard inside an existing page and don't want to install any SDK code, you can [embed a dashboard in an iframe](https://www.gooddata.ai/docs/cloud/embed-visualizations/iframes/). That works in any framework with no code at all. You can hide parts of the dashboard through URL query parameters (the filter bar, for example) and talk to it from the host page over [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). Reach for the React SDK or Web Components when you need more than that: your own layout, control over single widgets, state shared between components, or raw data in your own UI. ## [See a chart in five minutes](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/#see-a-chart-in-five-minutes) One command scaffolds a React app that already talks to a public demo workspace. The dev server proxies the API, so you don't set up CORS or sign in to anything: ```bash npx @gooddata/app-toolkit@latest init my-analytics-app ``` The [Quick start](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/index.md) explains what the command creates and how to swap the sample chart for a dashboard, or for a chart you define in code. It ends with the app pointed at your own workspace. To add GoodData.UI to a React codebase you already have instead, see [Add GoodData.UI to an existing React app](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/installation/index.md). Before you start, check your toolchain. [Platform compatibility](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/platform-compatibility/index.md) lists the Node.js, React, TypeScript, and bundler versions you need, and which SDK major works with which GoodData platform. ## [Core concepts](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/#core-concepts) A GoodData.UI application is built from a small set of primitives. You rarely need all of them at once, so pick what fits the screen you are building. **Backend and workspace context.** Every app is wrapped in a [`BackendProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.backendprovider/index.md) and [`WorkspaceProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.workspaceprovider/index.md). The backend object knows how to authenticate and talk to your GoodData instance. The workspace identifies which workspace's data you are rendering. Everything downstream, whether a chart, a filter, a dashboard, or a raw execution, reads both through React context, so you configure them once at the root. Configure them as described in [Integrations](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/index.md). **Visualizations.** A broad chart library: bar, column, line, area, pie, scatter, heatmap, funnel, waterfall, bullet, treemap, sankey, geo, and more, plus a pivot table and a repeater for tabular output. You can build charts in code by passing `measures`, `viewBy`, and `stackBy` props, or render any saved visualization by identifier with the universal [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md) component. Every chart shares one chart-config object for colors, legends, axes, and labels. See [Visualizations](https://www.gooddata.ai/docs/gooddata-ui/latest/visualisations/index.md). **Dashboards and plugins.** A full GoodData dashboard, with its layout, filters, widgets, and cross-filtering, drops into your app as a single [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component. For deeper integration, a plugin system lets you register custom widgets, replace built-in rendering, intercept drills, dispatch commands, and react to events. A full set of React hooks exposes the underlying Redux state machine for plugin authors. See [Dashboard & plugins](https://www.gooddata.ai/docs/gooddata-ui/latest/dashboard-plugins/index.md). **Filters.** The SDK ships ready-to-use React components for four filter types: attribute (single- and multi-select), date (absolute, relative, presets), measure-value, and ranking. Attribute filters also come as headless hooks, so you can build a custom UI while reusing the SDK's loading, paging, and selection logic. See [Filters](https://www.gooddata.ai/docs/gooddata-ui/latest/filters/index.md). **Data layer.** When a ready-made chart is not enough, the data layer gives you the raw data. [`Execute`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.execute/index.md), [`RawExecute`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.rawexecute/index.md), and [`ExecuteInsight`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.executeinsight/index.md) are render-prop components that run analytics queries; hooks such as `useExecutionDataView` and `useInsightDataView` expose the same power to function components. The data model covers measures, attributes, and a generated metadata catalog that gives you typed references to workspace objects. See [Data layer](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/index.md). **AI and high-level modules.** Ready-made product surfaces built on top of the SDK: an AI Assistant for conversational analytics, Semantic Search for meaning-based discovery, the Analytics Catalog explorer, and a Notifications panel for alerts. See [AI & modules](https://www.gooddata.ai/docs/gooddata-ui/latest/ai-modules/index.md). **Integrations.** Everything that sits between the SDK and your app shell: authentication, cross-origin setup, theming, drilling and interactivity, Angular and Vue adapters, and context hooks for reading backend, workspace, organization, and theme from any component. See [Integrations](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/index.md). ## [Building with an AI coding assistant?](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/#building-with-an-ai-coding-assistant) This documentation is also published as Markdown for tools such as Cursor, GitHub Copilot, or Claude. Point your assistant at [llms.txt](https://www.gooddata.ai/docs/gooddata-ui/llms.txt), the index of every page. The [LLMs](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/llms/index.md) page has the details. To let the assistant work with your GoodData workspace itself, connect it to the GoodData MCP server. The [LLMs](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/llms/index.md#give-the-assistant-access-to-your-workspace) page links the setup. --- # Add GoodData.UI to an existing React app > Add GoodData.UI to an existing React application by installing the packages, importing the styles, connecting the analytical backend, and rendering a dashboard your users pick from a list. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/installation/index.md GoodData.UI is distributed as npm packages under the `@gooddata` scope. This guide adds them to a React project you already have and ends with a dashboard on screen, chosen from a list of the dashboards in your workspace. Starting from scratch? The [Quick start](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/index.md) scaffolds a working app in one command instead. ## [Check the prerequisites](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/installation/#check-the-prerequisites) - **Node.js** `>=24.12.0` (an LTS release is recommended) - **React** 18 or 19 - **TypeScript** 5.8.3 or newer - A bundler that resolves ES modules. The packages are [ES modules only](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/esm-only/index.md) and have no CommonJS build - A **GoodData Cloud** or **GoodData.CN** workspace, and its hostname Using GoodData Legacy (the deprecated GoodData Platform)? Stay on GoodData.UI version 9. [Platform compatibility](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/platform-compatibility/index.md) explains why. ## [Install the packages](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/installation/#install-the-packages) > **Note:** > No access to npm? On GoodData.CN, your own deployment already serves GoodData.UI as the Web Components library, so you > can embed dashboards and visualizations without installing any package. See > [Web Components overview](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/index.md#load-the-library). Every GoodData.UI app needs the backend connector, the core UI package, and the data model: ```bash npm install @gooddata/sdk-backend-tiger @gooddata/sdk-ui @gooddata/sdk-model ``` Then add the packages for the components you plan to render. Install only what you use, because each one brings its own stylesheet and bundle weight: ```bash # Dashboard: renders a whole saved dashboard npm install @gooddata/sdk-ui-dashboard # Charts: BarChart, PieChart, LineChart, ... npm install @gooddata/sdk-ui-charts # Pivot table npm install @gooddata/sdk-ui-pivot # InsightView: renders any visualization saved in your workspace npm install @gooddata/sdk-ui-ext # Attribute, date, measure-value, and ranking filters npm install @gooddata/sdk-ui-filters # Shared UI primitives (buttons, dropdowns, overlays) the packages above rely on npm install @gooddata/sdk-ui-kit ``` If you prefer a single dependency, `@gooddata/sdk-ui-all` re-exports `@gooddata/sdk-model`, `@gooddata/sdk-backend-spi`, `@gooddata/sdk-ui`, and the charts, geo, pivot, filters, and [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md) packages. It does not include `@gooddata/sdk-ui-dashboard` or the AI packages, so install those separately. ## [Import the styles](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/installation/#import-the-styles) The components ship CSS you have to import once, typically in your app's entry file. Import only the packages you installed: ```tsx import "@gooddata/sdk-ui-kit/styles/css/main.css"; import "@gooddata/sdk-ui-filters/styles/css/main.css"; import "@gooddata/sdk-ui-charts/styles/css/main.css"; import "@gooddata/sdk-ui-pivot/styles/css/main.css"; import "@gooddata/sdk-ui-ext/styles/css/main.css"; import "@gooddata/sdk-ui-dashboard/styles/css/main.css"; ``` ## [Connect the backend](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/installation/#connect-the-backend) Create one analytical backend instance and hand it to the whole component tree through [`BackendProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.backendprovider/index.md). [`WorkspaceProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.workspaceprovider/index.md) names the workspace whose data you render. Every GoodData.UI component reads both from React context, so you set them up once at the root: ```tsx import { BackendProvider, WorkspaceProvider } from "@gooddata/sdk-ui"; import { ContextDeferredAuthProvider, redirectToTigerAuthentication, tigerFactory, } from "@gooddata/sdk-backend-tiger"; // The browser already holds a GoodData session (SSO, or you are signed in to GoodData Cloud). // When your app is served from a different origin than GoodData, call onHostname first: // tigerFactory().onHostname("https://your-instance.gooddata.com").withAuthentication(...) // and allow your app's origin in the CORS settings. const backend = tigerFactory().withAuthentication( new ContextDeferredAuthProvider(redirectToTigerAuthentication), ); export function App() { return ( ); } ``` [`ContextDeferredAuthProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-tiger/sdk-backend-tiger.contextdeferredauthprovider/index.md) is the right choice for production apps. It expects the user to be signed in already, and `redirectToTigerAuthentication` sends them to the GoodData sign-in flow when they aren't. [Authenticate to the backend](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/authentication/index.md) covers the other options, such as API tokens during development and JWT. If your app runs on a different origin than GoodData, read [Resolve cross-origin errors](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/cross-origin-issues/index.md) for the dev server proxy and the production CORS setup. ## [Render a dashboard your users pick](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/installation/#render-a-dashboard-your-users-pick) Most embedded apps don't hardcode a dashboard id. They ask the workspace which dashboards exist, show the titles in a sidebar or a menu, and render whichever one the user picks. The same `backend` you created above exposes that list, and the [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component accepts the picked identifier as a plain string. `useBackendStrict` and `useWorkspaceStrict` read the two providers from context. `useCancelablePromise` is the SDK's small hook for async calls in components. It tracks loading and error state and cancels the call if the component unmounts. Swap in your own data-fetching library if you already use one. ```tsx import { useState } from "react"; import { useBackendStrict, useCancelablePromise, useWorkspaceStrict } from "@gooddata/sdk-ui"; import { Dashboard } from "@gooddata/sdk-ui-dashboard"; export function Analytics() { const backend = useBackendStrict(); const workspace = useWorkspaceStrict(); const [selectedId, setSelectedId] = useState(); const { result: dashboards, status, error } = useCancelablePromise( { promise: () => backend.workspace(workspace).dashboards().getDashboards() }, [backend, workspace], ); if (status === "error") { return
Could not load dashboards: {String(error)}
; } if (status !== "success") { return
Loading dashboards...
; } const currentId = selectedId ?? dashboards[0]?.identifier; return (
{currentId ? :

No dashboards in this workspace yet.

}
); } ``` Each entry in the list has an `identifier`, a `title`, and a `description`, so you can build whatever navigation fits your app. Store the selected id in your router or global state if the dashboard should survive a page reload. The [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component brings the whole thing: layout, filter bar, widgets, drilling, and exports. The [Dashboard component](https://www.gooddata.ai/docs/gooddata-ui/latest/dashboard-plugins/dashboard/index.md) page lists every prop, and [Embed dashboards](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/embed-dashboards/index.md) shows how to react to its events and tune what it shows. ## [Optional: typed references with the metadata catalog](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/installation/#optional-typed-references-with-the-metadata-catalog) When you build charts in code rather than rendering saved ones, you reference measures and attributes by id. `@gooddata/catalog-export` can generate a TypeScript file with a typed constant for each object in your workspace, so a mistyped id fails at compile time: ```tsx import { BarChart } from "@gooddata/sdk-ui-charts"; import * as Md from "./catalog.js"; export function Revenue() { return ; } ``` The constant names come from your workspace, so `Md.Revenue` and `Md.Product.Default` stand in for whatever the exporter generated for you. Install the tool as a dev dependency, describe your workspace in a `gooddata` block in `package.json`, and give it an API token through the `TIGER_API_TOKEN` environment variable. [Export catalog](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/data-model/export-catalog/index.md) has the setup and the [Quick start](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/index.md#look-around-the-project) shows it wired into a project. ## [Next steps](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/installation/#next-steps) - The [Quick start](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/index.md) shows the same setup in a scaffolded app, then renders a chart built in code and reads raw data. - [Integrate into an existing application](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/backend-setup/index.md) is the long version of this page, including production CORS and authentication. - [Apply theming](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/apply-theming/index.md) makes the embedded dashboard match your app. - [Visualizations](https://www.gooddata.ai/docs/gooddata-ui/latest/visualisations/index.md) helps you pick a component when one chart is enough. --- # Quick start > Scaffold a GoodData.UI React app with app-toolkit, render a dashboard and a chart you define in code, read raw data, apply a theme, and connect your own workspace. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/index.md The fastest way to see GoodData.UI working is `@gooddata/app-toolkit`. It scaffolds a small React app that is already connected to a public demo workspace, so the first chart renders before you configure anything. This tutorial takes you from that command to your own data. You need Node.js `>=24.12.0` (an LTS release is recommended) and npm. You don't need a GoodData account yet. ## [Create the project](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/#create-the-project) Run the toolkit and answer its one prompt, the language (TypeScript or JavaScript): ```bash npx @gooddata/app-toolkit@latest init my-analytics-app ``` The toolkit unpacks the `react-app` template into `my-analytics-app/` and installs its dependencies with npm. Prefer yarn, or want to skip the prompt? Pass `--package-manager yarn`, `--language ts`, or `--skip-install`; `npx @gooddata/app-toolkit@latest init --help` lists every option. Start the dev server: ```bash cd my-analytics-app npm start ``` Your browser opens on `https://127.0.0.1:3000` with a page that says "Hello GoodWorld!" and renders a pie chart of product categories from the demo workspace. That chart is a saved visualization, rendered by the [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md) component. The dev server uses a self-signed certificate, so accept the browser's warning once. Set the `PORT` environment variable if 3000 is taken; the dev server fails rather than picking another port. ## [Look around the project](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/#look-around-the-project) There is no framework hiding anything. Every file is yours to edit: ```text my-analytics-app/ ├── package.json # Dependencies, scripts, and the gooddata block ├── vite.config.ts # Vite build and dev server, including the API proxy ├── index.html # Page that Vite serves; loads src/index.tsx ├── tsconfig.json # TypeScript configuration (TypeScript projects only) ├── .env # Holds TIGER_API_TOKEN, empty for now ├── .gitignore # Already ignores .env ├── README.md ├── public/ # Static files served as-is, currently the favicon └── src/ ├── index.tsx # React entry point; imports the SDK stylesheets ├── App.tsx # The sample component. Start here ├── backend.ts # Analytical backend instance and auth provider ├── catalog.ts # Generated workspace metadata, don't edit by hand ├── index.css # Styles for the sample page ├── typings.d.ts # Ambient types for SVG and CSS imports and WORKSPACE_ID └── assets/ # Logo and background image used by the sample page ``` If you chose JavaScript at the prompt, the same files use `.jsx` and `.js` extensions, including `vite.config.js`, and there's no `tsconfig.json` or `typings.d.ts`. Three files do most of the work: `src/App.tsx` is where you'll spend your time. It wraps the app in [`BackendProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.backendprovider/index.md) and [`WorkspaceProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.workspaceprovider/index.md) and renders a single [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md). Every step below edits this file, and each starts from the sample as the toolkit generated it, so take them in any order. > **Note:** > The term 'insight' is an obsolete expression for 'visualizations' but is still employed within the SDK's components and functions. That's why the catalog constant is `Insights` and the component is [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md). `src/catalog.ts` is generated. `@gooddata/catalog-export` reads your workspace and writes a typed constant for each visualization, dashboard, measure, and attribute. In TypeScript, a mistyped reference then fails at compile time rather than at runtime. Run `npm run refresh-md` to regenerate it. `package.json` carries a `gooddata` block that the rest of the setup reads: ```json "gooddata": { "hostname": "https://public-examples.gooddata.com", "workspaceId": "demo", "backend": "tiger", "catalogOutput": "src/catalog.ts" } ``` The dev server [proxies API calls](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/cross-origin-issues/index.md#proxy-the-api-in-development) to `hostname`, so the browser never makes a cross-origin request. Vite's `define` option injects `workspaceId` into the app as the `WORKSPACE_ID` global, and `refresh-md` writes to `catalogOutput`. You'll change the first two values when you [connect your own data](#connect-your-own-data). The scripts you'll use: - `npm start` starts the dev server. - `npm run build` bundles the production version of the app into `dist/`. - `npm run refresh-md` regenerates the [metadata catalog](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/data-model/export-catalog/index.md#applications-created-with-app-toolkit). ## [Render a dashboard](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/#render-a-dashboard) The sample renders one saved visualization. Let's render a whole saved dashboard instead, with its filters, layout, and cross-filtering. Two edits in `App.tsx`: 1. Import the [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component and the `Dashboards` catalog constants. ```diff - import { InsightView } from "@gooddata/sdk-ui-ext"; + import { Dashboard } from "@gooddata/sdk-ui-dashboard"; - import { Insights } from "./catalog.js"; + import { Dashboards } from "./catalog.js"; ``` 2. Swap the component in JSX and point it at a dashboard from the catalog. ```diff - + ``` The dev server reloads and the "1. Overview" dashboard from the demo workspace fills the page. Read more about the [Dashboard component](https://www.gooddata.ai/docs/gooddata-ui/latest/dashboard-plugins/dashboard/index.md). The catalog constant is handy here, but real apps usually don't hardcode a dashboard. They list the workspace's dashboards at runtime and render the one the user picks. [Add GoodData.UI to an existing React app](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/installation/index.md#render-a-dashboard-your-users-pick) shows that pattern. ## [Build a chart in code](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/#build-a-chart-in-code) You don't have to save a visualization first. Any chart component accepts measures and attributes straight from the catalog. Let's build the same pie chart by hand: 1. Import [`PieChart`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-charts/sdk-ui-charts.piechart/index.md) and the measure and attribute it needs. ```diff - import { InsightView } from "@gooddata/sdk-ui-ext"; + import { PieChart } from "@gooddata/sdk-ui-charts"; - import { Insights } from "./catalog.js"; + import { Category_1, PercentRevenue } from "./catalog.js"; ``` 2. Replace the component in JSX, passing a measure and an attribute to slice it by. ```diff - + ``` Every chart component works this way. You pass `measures` plus one or more attribute props such as `viewBy` or `stackBy`. Read more about [PieChart](https://www.gooddata.ai/docs/gooddata-ui/latest/visualisations/pie-chart/index.md) and the rest of the [visualizations](https://www.gooddata.ai/docs/gooddata-ui/latest/visualisations/index.md). ## [Read the raw data](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/#read-the-raw-data) Sometimes you want the numbers rather than a chart, maybe to feed your own visualization library or your app's business logic. The `useInsightDataView` hook runs the same execution a chart would and hands you the result. 1. Create `src/MyTable.tsx`, which renders the pie chart's data as a plain table: ```tsx import { useInsightDataView } from "@gooddata/sdk-ui"; import { idRef } from "@gooddata/sdk-model"; import { Insights } from "./catalog.js"; export function MyTable() { const { result, error, status } = useInsightDataView({ insight: idRef(Insights.ProductCategoriesPieChart), }); if (error) { console.error(error); return
Error...
; } if (status !== "success" || !result) { return
Loading...
; } const slices = result.data().slices().toArray(); return ( {slices.map((slice) => { const title = slice.sliceTitles().join(" - "); const value = slice .dataPoints() .map((dp) => dp.rawValue) .join(", "); return ( ); })}
{title} {value}
); } ``` 2. Render it from `App.tsx`: ```diff - import { InsightView } from "@gooddata/sdk-ui-ext"; + import { MyTable } from "./MyTable.js"; ``` ```diff - + ``` Read more about [useInsightDataView](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/use-insight-data-view/index.md) and the rest of the [data layer](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/index.md). ## [Apply a theme](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/#apply-a-theme) Theming makes embedded analytics look like part of your app rather than a widget dropped in. [`ThemeProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.themeprovider/index.md) loads the theme configured on your GoodData server and lets you override any part of it in code. In `App.tsx`: 1. Import [`ThemeProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.themeprovider/index.md). ```diff + import { ThemeProvider } from "@gooddata/sdk-ui-theme-provider"; ``` 2. Wrap the content inside [`WorkspaceProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.workspaceprovider/index.md). ```diff +
...
+
``` If your workspace has a custom theme defined on the server, it is applied now. 3. Override how tooltips look: ```diff - + ``` Read more about [theming](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/apply-theming/index.md) and try [the theming example in the gallery](https://gdui-examples.herokuapp.com/theming). ## [Connect your own data](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/#connect-your-own-data) So far the app has been reading the [public demo workspace](https://www.gooddata.ai/docs/cloud/getting-started/connect-data/#example-database), the same data a GoodData Cloud or GoodData.CN trial starts with. Point it at your workspace in four steps: 1. In `package.json`, replace `hostname` and `workspaceId`: ```diff - "hostname": "https://public-examples.gooddata.com", - "workspaceId": "demo", + "hostname": "https://", + "workspaceId": "", ``` 2. Create an [API token](https://www.gooddata.ai/docs/cloud/manage-organization/user-token/) and paste it into the `.env` file in the project root. The dev-server proxy attaches it to every API call, and `refresh-md` uses it too: ```bash TIGER_API_TOKEN= ``` The file is already listed in `.gitignore`. Never commit it. 3. Regenerate the catalog for the new workspace: ```bash npm run refresh-md ``` 4. Update `App.tsx`. The demo visualization no longer exists in your workspace, so pick one of yours from the regenerated `Insights` constants: ```diff - + } showTitle /> ``` Restart `npm start` and your own data renders. Putting the token in the dev proxy is fine for development only. Before you ship, switch to the sign-in flow described in [Authenticate to the backend](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/authentication/index.md) and allow your app's origin in [CORS settings](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/cross-origin-issues/index.md#allow-your-origin-in-production). ## [Next steps](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/#next-steps) Your app renders your data. From here, pick the guide that matches what you're building: - [Add GoodData.UI to an existing React app](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/installation/index.md) applies the same setup to a React app you already have. - [Embed dashboards](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/embed-dashboards/index.md) drops a full dashboard into your app and configures it. - [Apply theming](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/apply-theming/index.md) matches embedded analytics to your brand. - [Filters](https://www.gooddata.ai/docs/gooddata-ui/latest/filters/index.md) let users narrow down what a chart or dashboard shows. - The [Angular](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/angular-integration/index.md), [Vue](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/vue-integration/index.md), and [Next.js](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/nextjs-integration/index.md) guides take GoodData.UI outside a plain React app. --- # Platform compatibility > Which GoodData.UI major works with GoodData Cloud, GoodData.CN, and GoodData Legacy, the Node.js, React, and TypeScript versions it needs, and which frameworks it supports. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/platform-compatibility/index.md Use this page to confirm that your GoodData platform, your toolchain, and your frontend framework all work with the GoodData.UI version you are about to install. ## [GoodData platform](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/platform-compatibility/#gooddata-platform) | GoodData.UI | GoodData Cloud | GoodData.CN | GoodData Legacy | | :---------- | :------------- | :---------- | :-------------- | | **11** | Yes | Yes | No | | **10** | Yes | Yes | No | | **9** | Yes | Yes | Yes | Version 11 is the current major and the one this documentation describes. Version 10 reached end of support on March 26, 2026, and no longer receives fixes. > **GoodData Legacy:** > GoodData Legacy (the deprecated GoodData Platform) is supported only by GoodData.UI version 9. Stay on version 9 and do not upgrade to version 10 or higher. From version 10 onwards, GoodData.UI supports only GoodData Cloud and GoodData.CN. ## [Toolchain](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/platform-compatibility/#toolchain) | Technology | Requirement | | :------------- | :---------------------------------------- | | **Node.js** | `>=24.12.0` (an LTS release is recommended) | | **React** | 18 or 19 | | **TypeScript** | 5.8.3 or newer | | **Bundler** | Resolves ES modules and the `exports` field: Vite, webpack 5, Rollup, esbuild, Parcel 2, or Next.js | With `skipLibCheck` turned off, TypeScript older than 5.8.3 reports errors in the package typings. Every `@gooddata` package is published as [ES modules only](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/esm-only/index.md). There is no CommonJS build, so TypeScript needs `moduleResolution` set to `NodeNext` or `Bundler`, and Jest needs its ESM mode. The project that `@gooddata/app-toolkit` scaffolds is already configured this way. ## [Frameworks](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/platform-compatibility/#frameworks) GoodData.UI directly supports React, which is what the SDK is written for, and Web Components. The `gd-dashboard-embed`, `gd-insight-embed`, and `gd-ai-assistant` custom elements load from your GoodData server with one ` ``` The file must be served from the same origin as your app; the provider ignores messages from anywhere else. ## [Create the provider](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/renew-session-silently/#create-the-provider) Now extend [`ContextDeferredAuthProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-tiger/sdk-backend-tiger.contextdeferredauthprovider/index.md) and override `authenticate()`: ```ts import { type IAuthenticatedPrincipal, type IAuthenticationContext, isNotAuthenticated, type NotAuthenticatedHandler, } from "@gooddata/sdk-backend-spi"; import { ContextDeferredAuthProvider } from "@gooddata/sdk-backend-tiger"; const AUTH_FINISHED = "gooddata-auth-finished"; export class SilentRenewalAuthProvider extends ContextDeferredAuthProvider { // withAuthentication() remembers the first signed-in user and would never call authenticate() again. public override disablePrincipalCache = true; private renewal: Promise | undefined; constructor( private readonly gooddataHost: string, // "https://example.gooddata.com" private readonly callbackUrl: string, // "https://app.example.com/auth-callback.html" onNotAuthenticated: NotAuthenticatedHandler, private readonly timeoutMs = 15_000, ) { super(onNotAuthenticated); } public override async authenticate(context: IAuthenticationContext): Promise { try { // The cookie may still be valid, for example on the first call. return await super.authenticate(context); } catch (error) { if (!isNotAuthenticated(error)) { throw error; } } // One renewal at a time, however many requests failed. this.renewal ??= this.renewInIframe().finally(() => { this.renewal = undefined; }); await this.renewal; // Throws NotAuthenticated if the renewal didn't help; the SDK then calls onNotAuthenticated. return super.authenticate(context); } private renewInIframe(): Promise { return new Promise((resolve) => { const loginUrl = new URL("/appLogin", this.gooddataHost); loginUrl.searchParams.set("redirectTo", this.callbackUrl); const iframe = document.createElement("iframe"); iframe.hidden = true; iframe.src = loginUrl.href; const finish = () => { window.clearTimeout(timer); window.removeEventListener("message", onMessage); iframe.remove(); resolve(); }; const onMessage = (event: MessageEvent) => { if (event.origin === window.location.origin && event.data === AUTH_FINISHED) { finish(); } }; // A stuck or prompting identity provider never reaches the callback page. const timer = window.setTimeout(finish, this.timeoutMs); window.addEventListener("message", onMessage); document.body.appendChild(iframe); }); } } ``` The promise always resolves, also on timeout. The next `super.authenticate()` decides whether the renewal worked, so a failed renewal ends in your handler instead of a request that never returns. ## [Connect the provider to the backend](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/renew-session-silently/#connect-the-provider-to-the-backend) Pass the provider to the backend, with a handler for the case the renewal can't fix, for example a user whose identity provider session has ended too: ```ts import { redirectToTigerAuthentication, tigerFactory } from "@gooddata/sdk-backend-tiger"; import debounce from "lodash/debounce"; const authProvider = new SilentRenewalAuthProvider( "https://example.gooddata.com", `${window.location.origin}/auth-callback.html`, // Last resort: the full sign-in. Debounced, because every failed request calls it. debounce(redirectToTigerAuthentication, 500), ); const backend = tigerFactory().onHostname("https://example.gooddata.com").withAuthentication(authProvider); ``` Instead of the redirect, you can show a "Your session expired" message and let the user choose when to sign in. See [Write your own handler](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/authentication/index.md#write-your-own-handler). ## [Test the renewal](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/renew-session-silently/#test-the-renewal) 1. Sign in and open a page with GoodData components. 2. Delete the GoodData session cookies for the GoodData host in your browser's developer tools. 3. Change a filter or open another dashboard. In the Network tab you see a 401, a request to `/appLogin` from an iframe, the callback page, and the retried request with a 200. The page doesn't reload. If the retried request still fails, look at the iframe's requests: a blocked frame (`X-Frame-Options`, `frame-ancestors`), a blocked cookie, or a sign-in form from the identity provider means one of the requirements at the top of this page isn't met. ## [Retry your own API calls](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/renew-session-silently/#retry-your-own-api-calls) If your app also calls the GoodData API with `fetch`, those requests don't go through the SDK's retry. Wrap them the same way: on 401, call `backend.authenticate(true)` once and repeat the request. ```ts async function gooddataFetch(path: string, init: RequestInit = {}): Promise { const send = () => fetch(new URL(path, "https://example.gooddata.com"), { ...init, credentials: "include", // Without it, GoodData answers an expired session with a login page instead of 401. headers: { "X-Requested-With": "XMLHttpRequest", ...init.headers }, }); const response = await send(); if (response.status !== 401) { return response; } await backend.authenticate(true); // runs the renewal; throws if the user must sign in return send(); } ``` --- # Resolve cross-origin errors > Resolve CORS errors between your app and GoodData Cloud or GoodData.CN by proxying the API through your dev server or allowing your origin in production. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/cross-origin-issues/index.md The browser blocks a request when your app's origin differs from the GoodData origin and GoodData hasn't allowed that origin. You'll see it the moment an app on `http://localhost:3000` calls `https://your-org.cloud.gooddata.com` — the request fails with a CORS error, even though the same URL works when you paste it into the address bar. This is the browser's [same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) at work. There are two fixes, one per environment: - **In development, proxy the API through your dev server.** The browser sees a single origin, so no cross-origin request happens and no GoodData-side configuration is needed. - **In production, allow your app's origin on the GoodData side.** Add the origin to your organization's CORS settings. Loading a dashboard plugin from your own host is the reverse direction: the GoodData dashboard requests files from your server, so your host needs the CORS headers instead. See [Host the plugin](https://www.gooddata.ai/docs/gooddata-ui/latest/dashboard-plugins/develop-your-first-plugin/index.md#host-the-plugin). ## [Proxy the API in development](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/cross-origin-issues/#proxy-the-api-in-development) Your dev server forwards every request under `/api` to your GoodData host. As far as the browser is concerned the API lives at `http://localhost:3000/api`, so the request is same-origin and never triggers a preflight. For this to work, the backend must build origin-relative URLs. Create it without `onHostname`: ```ts import { ContextDeferredAuthProvider, redirectToTigerAuthentication, tigerFactory, } from "@gooddata/sdk-backend-tiger"; // no onHostname() — requests go to the dev server, which proxies them export const backend = tigerFactory().withAuthentication( new ContextDeferredAuthProvider(redirectToTigerAuthentication), ); ``` If you scaffolded your app with `@gooddata/app-toolkit`, this proxy is already configured — see [Quick start](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/index.md). ### [Vite](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/cross-origin-issues/#vite) Add a `server.proxy` entry to `vite.config.ts`: ```ts import react from "@vitejs/plugin-react"; import { defineConfig, loadEnv } from "vite"; const BACKEND_URL = "https://public-examples.gooddata.com"; // Everything that has to reach the backend rather than the dev server. const BACKEND_ENDPOINTS = "^/(api/|gdc/|account\\.html|truste\\.html|account/)"; export default defineConfig(({ mode }) => { // the third argument loads every variable, not just the VITE_-prefixed ones const env = loadEnv(mode, process.cwd(), ""); const apiToken = env["TIGER_API_TOKEN"]; return { plugins: [react()], server: { port: 3000, // Vite's dev server allows any origin by default. The proxy below injects your API token into // every backend request, so leaving CORS on would let any other page on localhost ride that // token through your dev server. Turn it off. cors: false, proxy: { [BACKEND_ENDPOINTS]: { target: BACKEND_URL, changeOrigin: true, // An empty string strips the cookie's Domain attribute, so the browser stores a host-only // cookie valid for whichever of 127.0.0.1 / localhost you opened the dev server on. cookieDomainRewrite: "", secure: false, headers: { // The literal string "null" is the serialized opaque origin. Setting it here, rather // than stripping the header in a proxyReq callback, also covers requests http-proxy // skips that callback for (any request carrying `Expect: 100-continue`). origin: "null", ...(apiToken ? { Authorization: `Bearer ${apiToken}` } : {}), }, }, }, }, }; }); ``` ### [webpack](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/cross-origin-issues/#webpack) Add a `devServer.proxy` entry to `webpack.config.cjs`. `webpack-dev-server` 5 takes an array of proxy rules: ```js require("dotenv").config(); const BACKEND_URL = "https://public-examples.gooddata.com"; // Everything that has to reach the backend rather than the dev server. const BACKEND_ENDPOINTS = /^\/(api\/|gdc\/|account\.html|truste\.html|account\/)/; const apiToken = process.env.TIGER_API_TOKEN; module.exports = { // ...the rest of your configuration devServer: { host: "127.0.0.1", port: 3000, proxy: [ { context: (pathname) => BACKEND_ENDPOINTS.test(pathname), target: BACKEND_URL, changeOrigin: true, // An empty string strips the cookie's Domain attribute, so the browser stores a host-only // cookie valid for whichever of 127.0.0.1 / localhost you bind the dev server to. cookieDomainRewrite: "", secure: false, headers: { // The literal string "null" is the serialized opaque origin. Setting it here, rather // than stripping the header in an onProxyReq callback, also covers requests http-proxy // skips that callback for (any request carrying `Expect: 100-continue`). origin: "null", ...(apiToken ? { Authorization: `Bearer ${apiToken}` } : {}), }, }, ], }, }; ``` Both configurations use the same options: - `changeOrigin` rewrites the `Host` header to the target host, so GoodData sees a request addressed to itself. - `cookieDomainRewrite: ""` strips the session cookie's `Domain` attribute, so the browser stores a host-only cookie that works no matter which of `127.0.0.1`/`localhost` you bind the dev server to — no need to keep it in sync with your dev server's host. - `secure: false` accepts a self-signed certificate, which you need when proxying to a local GoodData.CN instance. - Setting `headers.origin` to the string `"null"` stops the forwarded request from still advertising your dev server, which GoodData would read as cross-origin. Setting it as a static header, rather than removing it in a `proxyReq`/`onProxyReq` callback, also covers requests where the underlying `http-proxy` library skips that callback (any request carrying `Expect: 100-continue`). - The proxy path pattern covers more than `/api` — GoodData Cloud and GoodData.CN also serve some endpoints under `/gdc/`, `/account/`, `/account.html`, and `/truste.html`. > **cors: false is Vite-specific:** > Vite's dev server allows any origin by default, which would let any other page on `localhost` ride your injected API token through the proxy — hence `cors: false` in the Vite example above. `webpack-dev-server` doesn't enable permissive CORS by default, so it needs no equivalent setting. > **The API token is for development only:** > Injecting `Authorization` in the proxy keeps the token out of your bundle, but it's still a token with your permissions on a machine you control. Keep it in `.env`, keep `.env` out of version control, and never carry this proxy into production — deploy with a real authentication flow instead. See [Authenticate to the backend](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/authentication/index.md). > **Geo charts need one more rule:** > [`GeoChart`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-geo/sdk-ui-geo.geochart/index.md) and [`GeoPushpinChart`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-geo/sdk-ui-geo.geopushpinchart/index.md) load map glyphs and tiles from URLs returned by `/api/v1/location/style`. Those URLs are absolute and point at the GoodData host, so MapLibre fetches them cross-origin and the map stays blank. A working proxy has to intercept that response and rewrite its URLs to the dev-server origin. The `vite.config.ts` that `npx @gooddata/app-toolkit@latest init` generates already does this — start from it rather than writing the rewrite yourself. See [Quick start](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/index.md). ## [Allow your origin in production](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/cross-origin-issues/#allow-your-origin-in-production) A deployed app can't proxy its way around CORS, so add its origin to your organization's allowlist. ### [GoodData Cloud](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/cross-origin-issues/#gooddata-cloud) Configure CORS in the settings of your GoodData Cloud instance. If your instance is at `https://example.com`, the settings are at `https://example.com/settings`; you can also reach the page from your instance's home page. You can set the same `allowedOrigins` attribute through the organization API. For the accepted origin formats and the API call, see [Set up CORS for an organization](https://www.gooddata.ai/docs/cloud/manage-organization/set-up-cors-for-organization/). ### [GoodData.CN](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/cross-origin-issues/#gooddatacn) Set `allowedOrigins` on the organization the same way you would in GoodData Cloud. The GoodData.CN All-in-One image is already configured to allow cross-origin requests from an application running on `https://localhost:8443`, so a local development setup needs no change. > **CORS is not authentication:** > An allowed origin lets the request through; it doesn't make the request authenticated. Configure authentication separately — see [Authenticate to the backend](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/authentication/index.md) for API token and context-deferred flows. --- # Embedding > Embed GoodData content at different levels — full dashboards, individual visualizations, or custom visualizations using your own charting library — and apply theming. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/index.md Embed GoodData content at the level that fits your app: render an entire dashboard, show saved visualizations inline, or build custom visualizations on GoodData's execution pipeline. To embed without React, see [Web Components](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/index.md). --- # Embed dashboards > Choose among a built-in dashboard embedded through an iframe, the Dashboard component, and a custom dashboard by comparing their integration, customization, and implementation trade-offs. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/embed-dashboards/index.md GoodData.UI offers the following methods of building and embedding dashboards: - Embedding the [built-in dashboards](#built-in-dashboards) - Using the [Dashboard component](#dashboard-component) - Building [custom dashboards](#custom-dashboards) Each method has its advantages and disadvantages. This page explains how these methods differ, so you can pick the one that fits your use case. The methods are described from the simplest to the most complex. As a general rule, choose the simplest method that satisfies your needs over a more complex one that requires more code or special skills to implement. ## [Built-in dashboards](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/embed-dashboards/#built-in-dashboards) The dashboards built into GoodData Cloud and GoodData.CN are an end-to-end solution that covers both creation and consumption of dashboards. They offer many features out of the box, such as filtering, drilling, sharing, responsive layout, theming, email scheduling, export, and embedding. Users build new dashboards and edit existing ones in a drag-and-drop user interface. With the built-in dashboards, you have the following options: - Embed a dashboard in your application via an [iframe](#embed-a-dashboard-via-an-iframe). - Extend the default functionality with [dashboard plugins](#add-dashboard-plugins-to-a-dashboard). ### [Embed a dashboard via an iframe](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/embed-dashboards/#embed-a-dashboard-via-an-iframe) Embedding a dashboard via an iframe lets you integrate it in your application or an HTML page without any SDK code. For more information, see [Embed a dashboard via an iframe](https://www.gooddata.ai/docs/cloud/embed-visualizations/iframes/). Advantages: - Basic customization options via query parameters (for example, hide the filter bar) - Support for [dashboard plugins](#add-dashboard-plugins-to-a-dashboard) - Communication via the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) Disadvantages: - Limited customization options compared to the [Dashboard component](#dashboard-component) ### [Add dashboard plugins to a dashboard](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/embed-dashboards/#add-dashboard-plugins-to-a-dashboard) Dashboard plugins let you enhance the built-in dashboards with third-party libraries, custom visualizations, and custom behavior. With dashboard plugins, you run your own code within the dashboard. For more information, see [Dashboard plugins](https://www.gooddata.ai/docs/gooddata-ui/latest/dashboard-plugins/dashboard-plugins/index.md). Advantages: - Stable APIs that support GoodData Cloud and GoodData.CN - A [CLI tool](https://www.gooddata.ai/docs/gooddata-ui/latest/dashboard-plugins/develop-your-first-plugin/index.md) (`@gooddata/plugin-toolkit`) that scaffolds, builds, and links plugins - Support for TypeScript Disadvantages: - Limited customization options compared to the Dashboard component ## [Dashboard component](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/embed-dashboards/#dashboard-component) The [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component from `@gooddata/sdk-ui-dashboard` is a React component that renders dashboards created and saved in the built-in dashboards. It is the engine that the built-in dashboards use in the background, and you can customize it extensively. The [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component provides many built-in features. See [Dashboard component](https://www.gooddata.ai/docs/gooddata-ui/latest/dashboard-plugins/dashboard/index.md), which also lists its props. On its own, the component does not load the plugins linked to a dashboard. To render a dashboard together with its plugins, load it with `useDashboardLoader` as described in [Embed a dashboard with plugins](https://www.gooddata.ai/docs/gooddata-ui/latest/dashboard-plugins/embed-dashboard-with-plugins/index.md). Advantages: - Deep integration with React applications with a wide range of options (eventing API, customizations) - Extensive customization options such as custom visualization types, integration of third-party libraries, [dashboard plugins](#add-dashboard-plugins-to-a-dashboard), and so on - APIs that support GoodData Cloud and GoodData.CN - Support for TypeScript Disadvantages: - Some customization options are at alpha or beta stage - Limited styling or layout customization options compared to [custom dashboards](#custom-dashboards) ## [Custom dashboards](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/embed-dashboards/#custom-dashboards) If none of the methods above satisfies your needs, GoodData.UI lets you create your own dashboard. This is the most involved method of building dashboards, but it offers the most flexibility. Advantages: - Everything under your control (custom visualization types, custom layout, styling and so on) - The core `sdk-model` and `sdk-backend-*` packages compatible with any JavaScript/TypeScript framework - Multiple ready-to-use React components and hooks, both high- and low-level: charts, contexts, and more - The deepest integration with your application with a wide range of options - APIs that support GoodData Cloud and GoodData.CN Disadvantages: - More time needed for development and maintenance compared to the other methods - Deeper knowledge of GoodData required compared to the other methods - More complex (but not impossible) integration with applications without React --- # Embed visualizations > Render a visualization saved in Analyze with InsightView by getting its identifier, importing the component and styles, and setting its insight prop. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/embed-visualizations/index.md To embed a visualization saved in [Analyze](https://www.gooddata.ai/docs/cloud/create-visualizations/), use the [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/visualisations/insightview/index.md) component. > **Note:** > The term 'insight' is an obsolete expression for 'visualizations' but is still employed within the SDK's components and functions. 1. Get the identifier of the visualization. Copy it from the browser's address bar when the visualization is open in Analyze, or use the `Insights` constants that [catalog-export](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/data-model/export-catalog/index.md) generates. 2. Render [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md) with that identifier, and import the styles of `@gooddata/sdk-ui-ext` once in your app: ```tsx import { InsightView } from "@gooddata/sdk-ui-ext"; import "@gooddata/sdk-ui-ext/styles/css/main.css"; export function MyVisualization() { return (
); } ``` [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md) reads the backend and the workspace from [`BackendProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.backendprovider/index.md) and [`WorkspaceProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.workspaceprovider/index.md); pass the `backend` and `workspace` props to override them. See [Integrate into an existing application](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/backend-setup/index.md#provide-the-backend-and-workspace). For every prop of the component, see [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/visualisations/insightview/index.md#props). --- # How custom visualizations get data > Understand the hooks, components, and backend API that feed custom visualizations, and how series, slices, and data points structure the result. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/custom-visualizations/index.md With GoodData.UI, you can build your own visual components on top of GoodData data. This page explains the data side: which APIs fetch the data, how to describe what you need, and how to read the result. For a worked example that also replaces the filters and adds an export, follow [Build a fully custom frontend](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/build-custom-frontend/index.md). > **Note:** > This page builds on the [execution model](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/execution/execution-model/index.md). The [catalog-export](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/data-model/export-catalog/index.md) tool generates typed constants for your measures and attributes, which make the data specification easier to read. ## [Ways to get the data](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/custom-visualizations/#ways-to-get-the-data) You can get the data for a custom visualization with React [hooks](#react-hooks), React [components](#react-components), or the [execution API](#execution-api). Hooks and components have similar APIs and capabilities. For more complex scenarios, for example when one execution depends on another, hooks avoid nesting components inside each other. ### [React hooks](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/custom-visualizations/#react-hooks) - [`useExecutionDataView`](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/use-execution-data-view/index.md) fetches the data you describe with [series and slices](#series-and-slices) or with a [custom execution](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/execution/execution-model/index.md), and reports the loading status and errors. - [`useInsightDataView`](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/use-insight-data-view/index.md) fetches the data of a visualization saved in [Analyze](https://www.gooddata.ai/docs/cloud/create-visualizations/), so you can render it with your own component. It's [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/visualisations/insightview/index.md) without the view part. ### [React components](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/custom-visualizations/#react-components) - [`Execute`](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/execute/index.md) is the component counterpart of `useExecutionDataView` for data described with [series and slices](#series-and-slices). - [`RawExecute`](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/raw-execute/index.md) is the component counterpart of `useExecutionDataView` for a [custom execution](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/execution/execution-model/index.md). - [`ExecuteInsight`](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/execute-insight/index.md) is the component counterpart of `useInsightDataView`: it fetches the data of a saved visualization. Each of them fetches the result and reports the loading status and errors. ### [Execution API](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/custom-visualizations/#execution-api) Without hooks or components, you can get the data directly from the analytical backend instance. See [custom executions](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/execution/execution-model/index.md). ## [Series and slices](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/custom-visualizations/#series-and-slices) The execution hooks and components describe the data as series and slices. Two examples show how they relate. ### [Tabular data](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/custom-visualizations/#tabular-data) Imagine a custom table component that shows one row for each value of the attribute `A1`. Each row has two columns, one for the measure `M1` and one for the measure `M2`. Here, the data series are the two measures `M1` and `M2`, and the slices come from the attribute `A1`. Now the table becomes more dynamic: for each value of the attribute `A2`, it includes two columns, one for each measure, `M1` and `M2`. Here, the data series are the measures `M1` and `M2`, **scoped** to the values of the attribute `A2`. On top of that, these columns are sliced by the values of the attribute `A1`. ### [Scalars](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/custom-visualizations/#scalars) Imagine a custom headline component that shows several key figures, each calculated from a different measure: `M1`, `M2`, and `M3`. Here, the data series are the measures `M1`, `M2`, and `M3`, and there are no data slices at all. ## [Working with the results](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/custom-visualizations/#working-with-the-results) The result instance has methods for convenient data access: - `result.data().series()` returns the result as a collection of data series. - `result.data().slices()` returns the result as a collection of data slices. - You can iterate a collection with a `for-of` loop, or turn it into an array and use the usual array functions. - For each series or slice, you can iterate its data points: - Iterating the data points of a series gives you one data point per slice. - Iterating the data points of a slice gives you one data point per series. - Each data point holds the data and the metadata: the series descriptor and the slice descriptor. You can read either the raw or the formatted value. > **Avoid the raw backend result:** > The result instance also exposes the raw result from the backend. Its structure is an implementation detail, so read the data through series and slices instead. --- # Build a fully custom frontend > Build an analytics screen with no GoodData visual components, only your own markup, by driving an execution, a headless attribute filter, a date filter, and a CSV export through GoodData.UI hooks. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/build-custom-frontend/index.md Sometimes nothing GoodData.UI draws should be visible: the charts come from your design system, the filters are your own controls, and the layout is yours. GoodData.UI still knows the semantic model, runs the executions, loads attribute values, caches, and exports. You only render. In this tutorial you build a "Revenue by category" screen with a bar list drawn in plain HTML, a region filter rendered as checkboxes, a period dropdown, and a CSV download. No `@gooddata/sdk-ui-charts` or `@gooddata/sdk-ui-filters` component is involved, only hooks. You need the project from the [Quick start](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/index.md), or any React app with [`BackendProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.backendprovider/index.md), [`WorkspaceProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.workspaceprovider/index.md), and a generated catalog, as set up in [Installation](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/installation/index.md). You'll use these APIs: | To | Use | From | | :-- | :-- | :-- | | Run a query and read the result | `useExecutionDataView` | `@gooddata/sdk-ui` | | Load attribute values and manage a selection | `useAttributeFilterController` | `@gooddata/sdk-ui-filters` | | Define filters in code | `newNegativeAttributeFilter`, `newRelativeDateFilter`, `newAllTimeFilter` | `@gooddata/sdk-model` | | Export the same query | `useDataExport` with `useBackendStrict` and `useWorkspaceStrict` | `@gooddata/sdk-ui` | | Refer to workspace objects by name | the generated catalog | `@gooddata/catalog-export` | The code below uses the demo workspace's catalog constants: `Revenue`, `Category_1` (the product category; the catalog adds `_1` because the workspace has two attributes titled Category), `Region`, and `DateDatasets.Date`. In your own workspace, pick the equivalents from your `catalog.ts`. ## [Draw the data yourself](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/build-custom-frontend/#draw-the-data-yourself) Create `src/RevenueByCategory.tsx`. The hook runs one execution: the measure in `seriesBy`, the attribute to break it down by in `slicesBy`, and whatever filters the parent passes. The result has one slice per category with a single data point. `rawValue` is the number you compute with, `formattedValue()` the string to show, formatted the way the measure is defined in the workspace. ```tsx import type { IFilter } from "@gooddata/sdk-model"; import { useExecutionDataView } from "@gooddata/sdk-ui"; import { Category_1, Revenue } from "./catalog.js"; interface IRevenueByCategoryProps { filters: IFilter[]; } export function RevenueByCategory({ filters }: IRevenueByCategoryProps) { const { result, status, error } = useExecutionDataView( { execution: { seriesBy: [Revenue], slicesBy: [Category_1], filters, }, }, [filters], ); if (status === "error") { return

Could not load revenue: {error?.message}

; } if (status !== "success" || !result) { return

Loading…

; } const slices = result.data().slices().toArray(); const values = slices.map((slice) => Number(slice.dataPoints()[0]?.rawValue ?? 0)); const max = Math.max(0, ...values); return (
    {slices.map((slice, index) => (
  1. {slice.sliceTitles().join(" / ")}
    {slice.dataPoints()[0]?.formattedValue()}
  2. ))}
); } ``` The second argument, `[filters]`, is the dependency list. The hook runs again when a value in it changes, compared by reference, so the parent has to memoize the array. Render the component from `App.tsx` in place of the sample [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md): ```diff - import { InsightView } from "@gooddata/sdk-ui-ext"; + import { RevenueByCategory } from "./RevenueByCategory.js"; ``` ```diff - + ``` Four bars appear, one per category, with revenue formatted as currency. Read more about [useExecutionDataView](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/use-execution-data-view/index.md) and the [series and slices](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/custom-visualizations/index.md#access-custom-visualization-data) behind `slices()`. ## [Add a headless attribute filter](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/build-custom-frontend/#add-a-headless-attribute-filter) An attribute filter needs the attribute's values, paging for long lists, a selection the user is editing, and the selection they last applied. `useAttributeFilterController` manages all of that and leaves the rendering to you. Create `src/RegionFilter.tsx`: ```tsx import { useAttributeFilterController } from "@gooddata/sdk-ui-filters"; import { type IAttributeElement, type IAttributeFilter, newNegativeAttributeFilter } from "@gooddata/sdk-model"; import { Region } from "./catalog.js"; interface IRegionFilterProps { onApply: (filter: IAttributeFilter) => void; } export function RegionFilter({ onApply }: IRegionFilterProps) { const { isInitializing, elements, workingSelectionElements, isWorkingSelectionInverted, onSelect, onApply: applySelection, } = useAttributeFilterController({ // A negative filter with nothing excluded means "all regions". filter: newNegativeAttributeFilter(Region, []), onApply: (filter) => onApply(filter), }); if (isInitializing) { return

Loading regions…

; } const isChecked = (element: IAttributeElement) => { const inWorkingSelection = workingSelectionElements.some((el) => el.uri === element.uri); return isWorkingSelectionInverted ? !inWorkingSelection : inWorkingSelection; }; const toggle = (element: IAttributeElement) => { // Keep the selection mode and add or remove the value from the working list. const inWorkingSelection = workingSelectionElements.some((el) => el.uri === element.uri); const next = inWorkingSelection ? workingSelectionElements.filter((el) => el.uri !== element.uri) : [...workingSelectionElements, element]; onSelect(next, isWorkingSelectionInverted); }; return (
Region {elements.map((element) => ( ))}
); } ``` Two details matter here. The initial filter is a negative filter with an empty list, which GoodData.UI reads as "everything selected", and the controller reports it as an inverted selection: the working list holds the excluded values. The `toggle` function keeps that mode and only adds or removes the clicked value from the list, so values on pages you haven't loaded yet stay selected. Calling the controller's `onApply` commits the working selection and calls your `onApply` with an [`IAttributeFilter`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-model/sdk-model.iattributefilter/index.md) that executions understand. The controller loads elements in pages; for attributes with many values, call `onLoadNextElementsPage` when the user scrolls, as described in [useAttributeFilterController](https://www.gooddata.ai/docs/gooddata-ui/latest/filters/use-attribute-filter-controller/index.md). ## [Wire the filters into the execution](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/build-custom-frontend/#wire-the-filters-into-the-execution) Now hold the filters in state and pass them down. A period dropdown adds a date filter built in code. `newRelativeDateFilter` takes the date dataset, a granularity, and a range of periods where `0` is the current one, so `-11` to `0` is the last 12 months and `-1` to `-1` is last year. `newAllTimeFilter` removes the date restriction. Replace `App.tsx`: ```tsx import { type IAttributeFilter, type IDateFilter, newAllTimeFilter, newRelativeDateFilter } from "@gooddata/sdk-model"; import { BackendProvider, WorkspaceProvider } from "@gooddata/sdk-ui"; import { useMemo, useState } from "react"; import { backend } from "./backend.js"; import { DateDatasets } from "./catalog.js"; import { RegionFilter } from "./RegionFilter.js"; import { RevenueByCategory } from "./RevenueByCategory.js"; // WORKSPACE_ID is the global that the Quick start project injects with Vite's `define`. // In another app, use your workspace ID string. const workspaceId = WORKSPACE_ID; const periods = { all: newAllTimeFilter(DateDatasets.Date.ref), last12Months: newRelativeDateFilter(DateDatasets.Date.ref, "GDC.time.month", -11, 0), lastYear: newRelativeDateFilter(DateDatasets.Date.ref, "GDC.time.year", -1, -1), } satisfies Record; export function App() { const [regionFilter, setRegionFilter] = useState(); const [period, setPeriod] = useState("all"); const filters = useMemo( () => [periods[period], ...(regionFilter ? [regionFilter] : [])], [period, regionFilter], ); return (

Revenue by category

); } ``` Uncheck a region and click **Apply**, or pick a period, and the bars update. The `useMemo` around `filters` is what makes that efficient: the array only changes when a filter changes, so the execution runs exactly then. Every filter you can build in code is listed in [Filters](https://www.gooddata.ai/docs/gooddata-ui/latest/filters/index.md), and the [execution model](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/execution/execution-model/index.md) explains how filters, measures, and attributes combine. ## [Export the same data](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/build-custom-frontend/#export-the-same-data) Export reuses the execution definition rather than the rendered result. Build an [`IPreparedExecution`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ipreparedexecution/index.md) from the same items and filters, and hand it to `useDataExport`, which runs it, asks the backend for a CSV, and returns the download URI. Render the URI as a link rather than opening it from a callback: the export can take longer than the browser's window for user-initiated popups. Create `src/ExportButton.tsx`: ```tsx import type { IFilter } from "@gooddata/sdk-model"; import { useBackendStrict, useDataExport, useWorkspaceStrict } from "@gooddata/sdk-ui"; import { useMemo, useState } from "react"; import { Category_1, Revenue } from "./catalog.js"; interface IExportButtonProps { filters: IFilter[]; } export function ExportButton({ filters }: IExportButtonProps) { const backend = useBackendStrict(); const workspace = useWorkspaceStrict(); const [requestCount, setRequestCount] = useState(0); // Build the execution only after the first click; useDataExport stays "pending" while it is null. const execution = useMemo( () => requestCount > 0 ? backend.workspace(workspace).execution().forItems([Revenue, Category_1], filters) : null, [requestCount, backend, workspace, filters], ); const { status, result: downloadUri, error, } = useDataExport( { execution, exportConfig: { format: "csv", title: "revenue-by-category" }, }, [execution], ); return ( <> {status === "success" && downloadUri ? Download the CSV : null} {status === "error" ?

{error?.message}

: null} ); } ``` Render `` under the bars in `App.tsx`. `useBackendStrict` and `useWorkspaceStrict` read the providers at the top of the app, so the button needs no props beyond the filters. ## [Where to go next](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/build-custom-frontend/#where-to-go-next) The screen is yours end to end, and the pattern scales: one hook per query, filters in state, memoized arrays as dependencies. - [How custom visualizations get data](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/custom-visualizations/index.md) explains series and slices for shapes beyond one measure by one attribute, and the [`Execute`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.execute/index.md) components when you prefer render props to hooks. - [Result structure](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/execution/result-structure/index.md) and [Run a custom execution](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/execution/custom-executions/index.md) cover dimensions, totals, sorting, and running executions outside React. - [useAttributeFilterHandler](https://www.gooddata.ai/docs/gooddata-ui/latest/filters/use-attribute-filter-handler/index.md) is the lower-level API under the controller, for filter UIs with their own state management. - The hooks and the ready-made components share the same providers, so you can mix them. An [InsightView](https://www.gooddata.ai/docs/gooddata-ui/latest/visualisations/insightview/index.md) next to your bars reads the same `filters` array. - Before you ship, replace the development token with a [sign-in flow](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/authentication/index.md). --- # Apply theming > Apply custom themes to GoodData.UI dashboards and visualizations by wrapping your app in ThemeProvider. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/apply-theming/index.md To apply theming in GoodData.UI, wrap your application in the [`ThemeProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.themeprovider/index.md) component. It loads the theme selected in your workspace, or a theme you pass in, and applies it to your dashboards and visualizations (see [Create custom themes](https://www.gooddata.ai/docs/cloud/customize-appearance/create-custom-themes/)). To load a theme from your workspace, [`ThemeProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.themeprovider/index.md) needs a `backend` and a `workspace` — either from the surrounding providers or from its own props (see [Integrate into an existing application](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/backend-setup/index.md)). > **Warning:** > Always place [`ThemeProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.themeprovider/index.md) at the root of your application. A theme is applied globally to everything on the page: when you wrap one visual component in [`ThemeProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.themeprovider/index.md), everything else on the page is themed too, even though it is not wrapped. ## [Use ThemeProvider with backend and workspace providers](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/apply-theming/#use-themeprovider-with-backend-and-workspace-providers) When [`ThemeProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.themeprovider/index.md) is rendered inside [`BackendProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.backendprovider/index.md) and [`WorkspaceProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.workspaceprovider/index.md), you do not need to pass it any additional props. ```tsx import { BackendProvider, WorkspaceProvider } from "@gooddata/sdk-ui"; import { ThemeProvider } from "@gooddata/sdk-ui-theme-provider"; ``` ## [Use ThemeProvider without backend and workspace providers](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/apply-theming/#use-themeprovider-without-backend-and-workspace-providers) Without [`BackendProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.backendprovider/index.md) and [`WorkspaceProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.workspaceprovider/index.md) in the tree, pass the `backend` and `workspace` props to [`ThemeProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.themeprovider/index.md) directly. ```tsx import { ThemeProvider } from "@gooddata/sdk-ui-theme-provider"; ``` ## [Use ThemeProvider with a custom theme](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/apply-theming/#use-themeprovider-with-a-custom-theme) You can define a custom theme and pass it to [`ThemeProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.themeprovider/index.md) through the `theme` prop; the provider then uses it instead of loading one from the workspace. For all available properties, see the [`ITheme`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-model/sdk-model.itheme/index.md) interface (beta) in the API reference. Usage: ```tsx import { ThemeProvider } from "@gooddata/sdk-ui-theme-provider"; ``` Declaration: ```ts import type { ITheme } from "@gooddata/sdk-model"; export const customTheme: ITheme = { button: { borderRadius: "15", dropShadow: true, textCapitalization: true, }, modal: { borderColor: "#1b4096", borderRadius: "5", borderWidth: "2", dropShadow: false, outsideBackgroundColor: "#e8cda2", title: { color: "#1b4096", lineColor: "#000", }, }, palette: { error: { base: "#ff2e5f", }, primary: { base: "#eba12a", }, success: { base: "#13ed4d", }, warning: { base: "#ddff19", }, }, tooltip: { backgroundColor: "#101050", color: "#fff", }, typography: { font: "url(https://cdn.jsdelivr.net/npm/roboto-font@0.1.0/fonts/Roboto/roboto-regular-webfont.ttf)", fontBold: "url(https://cdn.jsdelivr.net/npm/roboto-font@0.1.0/fonts/Roboto/roboto-bold-webfont.ttf)", }, }; ``` ## [Props](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/apply-theming/#props) | Prop | Type | Required | Description | Inherited from | |------|------|----------|-------------|----------------| | [`backend`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.ithemeproviderprops/index.md#ithemeproviderpropsbackend) | [`IAnalyticalBackend`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md) | No | Analytical backend, from which the ThemeProvider will obtain selected theme object. | — | | [`children`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.ithemeproviderprops/index.md#ithemeproviderpropschildren) | `ReactNode` | No | React children | — | | [`enableComplementaryPalette`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.ithemeproviderprops/index.md#ithemeproviderpropsenablecomplementarypalette) | `boolean` | No | Flag determining whether the complementary palette is enabled or not. | — | | [`modifier`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.ithemeproviderprops/index.md#ithemeproviderpropsmodifier) | [`ThemeModifier`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.thememodifier/index.md) | No | If provided it is called with loaded theme to allow its modification according to the app needs. | — | | [`removeGlobalStylesOnUnmout`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.ithemeproviderprops/index.md#ithemeproviderpropsremoveglobalstylesonunmout) | `boolean` | No | Should ThemeProvider remove global styles during the unmount phase? | — | | [`theme`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.ithemeproviderprops/index.md#ithemeproviderpropstheme) | [`ITheme`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-model/sdk-model.itheme/index.md) | No | Theme that will be used if defined. | — | | [`workspace`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.ithemeproviderprops/index.md#ithemeproviderpropsworkspace) | `string` | No | Identifier of analytical workspace, from which the ThemeProvider will obtain the selected theme identifier | — | --- # Web Components > Custom HTML elements that embed GoodData dashboards, visualizations, and the AI assistant in any framework or vanilla HTML, without a direct React dependency. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/index.md Custom HTML elements (`gd-dashboard-embed`, `gd-insight-embed`, `gd-ai-assistant`, and others) that work in any framework or in plain HTML. A single ` ``` > **Note:** > The Web Components library runs GoodData.UI under the hood. It loads React and the other dependencies it needs, in an isolated scope that doesn't conflict with other JavaScript in your app. ## [Choosing the right embedding option](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/#choosing-the-right-embedding-option) GoodData offers several ways to embed analytics: iframe embedding for dashboards, and the GoodData.UI React library for dashboards and visualizations. The Web Components library sits between the two. It's more flexible than an iframe and simpler to integrate than the React library. ### [When to use the Web Components library](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/#when-to-use-the-web-components-library) - You don't want iframe embedding, because of the overhead it creates or because of your company's security and compliance requirements. - You want to embed a single visualization; iframe embedding works only for a complete dashboard. - Your host application uses Angular, Vue, or another non-React framework. - Your application uses a React version that GoodData.UI doesn't support. - You can't or don't want to install packages from npm, for example on a GoodData.CN deployment in a restricted network. Your GoodData.CN instance serves the library itself; see [How the library loads](#load-the-library). ### [When to use an iframe instead](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/#when-to-use-an-iframe-instead) If you want the simplest possible dashboard embedding and don't need deep integration between the host application and the dashboard, an iframe is enough. An iframe also fits when you want dashboard plugins but can't provide a Module Federation runtime, which `gd-dashboard-embed` needs to load plugins linked to the dashboard. The legacy `gd-dashboard` element doesn't load plugins at all. ### [When to use the GoodData.UI React library instead](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/#when-to-use-the-gooddataui-react-library-instead) If the host application is already written in React, use the GoodData.UI React components. They are more flexible, and you avoid loading a second copy of React and ReactDOM. ## [How the library loads](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/#load-the-library) The library is a JavaScript module that your GoodData instance serves at `/components/.js`. You load it with a ` ``` ## [Prerequisites and limitations](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/#prerequisites-and-limitations) ### [Supported web browsers](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/#supported-web-browsers) The library needs a browser that supports custom elements; it doesn't work in older browsers such as Internet Explorer. See the [Custom Elements browser compatibility](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry#browser_compatibility) table on MDN. ### [Cross-origin requests](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/#cross-origin-requests) The script makes requests from your application's origin to the GoodData host, so the organization must allow your origin. See [Allow your origin in production](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/cross-origin-issues/index.md#allow-your-origin-in-production). ### [Content Security Policy (CSP)](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/#content-security-policy-csp) If your server sends [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) headers, add the GoodData host to the `script-src`, `style-src`, `font-src`, and `img-src` policies. For a GoodData host at `example.gooddata.com`, the policy could look like this: ```text script-src 'self' 'unsafe-inline' example.gooddata.com; img-src 'self' data: blob: example.gooddata.com; style-src 'self' 'unsafe-inline' example.gooddata.com; font-src 'self' data: example.gooddata.com; ``` ### [Third-party cookie blocking](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/#third-party-cookie-blocking) The script authenticates its requests to the GoodData host with the GoodData session cookie. When your application runs on a different site than GoodData, the browser treats that cookie as a third-party cookie, and browsers that block third-party cookies make the requests fail even though the user signed in. Serve GoodData from the same site as your application, for example `https://analytics.example.com` next to `https://example.com`. [Cookies across sites](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/authentication/index.md#cookies-across-sites) explains the problem and the other fixes. ### [Dashboard plugins](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/#dashboard-plugins) The legacy `gd-dashboard` element doesn't load dashboard plugins. The `gd-dashboard-embed` element does; see [Dashboard plugins](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/index.md#dashboard-plugins). Plugins linked to the dashboard on the server need the `moduleFederationIntegration` property; without it, only the plugins you pass through `extraPlugins` load. If you can provide neither, embed the dashboard with an iframe or the React [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component. ### [CSS class name collisions](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/#css-class-name-collisions) The script adds a CSS `` to the `` of your page when it loads. The library's CSS class names can, rarely, collide with your own. If that happens, [open a GitHub issue](https://github.com/gooddata/gooddata-ui-sdk/issues/new). --- # Dashboard and visualization embed elements > Embed a dashboard or a saved visualization with gd-dashboard-embed and gd-insight-embed, and drive them through properties, methods, and events. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/index.md `gd-dashboard-embed` and `gd-insight-embed` are the recommended custom elements for new integrations. They render the same React components as the legacy `gd-dashboard` and `gd-insight` elements, the [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component and [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/visualisations/insightview/index.md), and add JavaScript properties, methods, lifecycle events, and dashboard plugin loading. The legacy elements stay registered for existing integrations; see the [dashboard](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/dashboard-custom-element/index.md) and [visualization](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/insight-custom-element/index.md) pages. > **Note:** > The term 'insight' is an obsolete expression for 'visualizations' but is still employed within the SDK's components and functions. ## [Embed elements compared with the legacy elements](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/#embed-elements-compared-with-the-legacy-elements) | | `gd-dashboard-embed`, `gd-insight-embed` | `gd-dashboard`, `gd-insight` | | --- | --- | --- | | Inputs | Attributes for the initial setup, JavaScript properties for everything after | Attributes only | | `dashboard` / `insight` identity | Locked after the first render; a change dispatches `gd-error` | Any attribute change re-renders, including the identity | | Methods | `refresh()`; `replaceFilters()` on the dashboard | None | | Lifecycle events | `gd-ready`, `gd-error`; `gd-warning` on the dashboard | None; load failures go to `console.error` | | Dashboard plugins | Loaded, controlled by `pluginMode` | Not loaded | | Backend and workspace | The shared context, or a per-element `context` property | The shared context and the `workspace` attribute | ## [Minimal example](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/#minimal-example) ```html ``` Load the library and sign the user in as described in the [Web Components overview](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/index.md) and [Web Components authentication](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-auth/index.md). ## [How inputs resolve](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/#how-inputs-resolve) Both elements follow the same rules: - **Attributes bootstrap, properties drive.** An attribute is read at render time. Once you assign the JavaScript property of the same name, the property wins over the attribute, even when you assign `undefined`. - **Changes are batched.** Several property assignments or attribute changes in the same task cause one re-render. - **Identity is immutable.** After the first successful render, `dashboard` (or `insight`) can't change through the attribute or the property. The element dispatches `gd-error` with `phase: "invalidUsage"` and the message `"dashboard" cannot change after first render.`, and keeps rendering the original object. To show a different dashboard or visualization, mount a new element. - **Context merges field by field.** The `context` property `{ backend, workspaceId?, mapboxToken?, agGridToken? }` overrides the shared context set from the script URL or with [`setContext`](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-auth/index.md). The workspace resolves as `context.workspaceId`, then the `workspace` attribute, then the shared context. - **Loading state.** Until a backend is known and the component code has loaded, the element renders the [`LoadingComponent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.loadingcomponent/index.md) from `@gooddata/sdk-ui`. If either fails, it dispatches `gd-error` with `phase: "init"`. - **Disconnecting unmounts.** Removing the element from the DOM unmounts its React tree. Reconnecting renders it again with the same, still locked, identity. ## [`gd-dashboard-embed`](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/#gd-dashboard-embed) Loads the dashboard through the same loader as the [`useDashboardLoader`](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-dashboard-loader/index.md) hook and renders the [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component. ### Attributes | Attribute | Value | Description | | --- | --- | --- | | `dashboard` | Dashboard ID | Required unless you set the `dashboard` property. Locked after the first render. | | `workspace` | Workspace ID | Overrides the workspace of the shared context. | | `locale` | Locale code such as `en-US` | Sets `config.locale`. Ignored once you assign the `config` property. | | `readonly` | Boolean attribute | Sets `config.isReadOnly: true`. Ignored once you assign the `config` property. | | `mapbox` | Mapbox access token | Sets `config.mapboxToken`; overrides `mapboxToken` from the context. Ignored once you assign the `config` property. | | `agGrid` | AG Grid license key | Sets `config.agGridToken`; overrides `agGridToken` from the context. Ignored once you assign the `config` property. | ### Properties | Property | Type | Description | | --- | --- | --- | | `context` | `{ backend, workspaceId?, mapboxToken?, agGridToken? }` | Per-element backend and workspace. Missing fields fall back to the shared context. | | `config` | [`DashboardConfig`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboardconfig/index.md) | The complete `config` prop of the [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component. Replaces the config built from the attributes and the context tokens, so include `locale`, `isReadOnly`, `mapboxToken`, and `agGridToken` yourself when you need them. | | `dashboard` | `string` | Dashboard ID. Same identity rule as the attribute. | | `pluginMode` | `"all" \| "embeddedOnly" \| "backendOnly" \| "disabled"` | Which dashboard plugins load. See [Dashboard plugins](#dashboard-plugins). | | `extraPlugins` | `IEmbeddedPlugin \| IEmbeddedPlugin[]` | Plugins bundled with your application. | | `moduleFederationIntegration` | [`ModuleFederationIntegration`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.modulefederationintegration/index.md) | Module Federation runtime needed to load plugins linked to the dashboard on the server. | ### [Dashboard plugins](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/#dashboard-plugins) Unlike `gd-dashboard`, the embed element loads [dashboard plugins](https://www.gooddata.ai/docs/gooddata-ui/latest/dashboard-plugins/dashboard-plugins/index.md). `pluginMode` selects which ones: | `pluginMode` | Plugins linked on the server | `extraPlugins` | Needs `moduleFederationIntegration` | | --- | --- | --- | --- | | Not set | Loaded when `moduleFederationIntegration` is set; otherwise skipped with a `gd-warning` | Loaded | No | | `"all"` | Loaded | Loaded | Yes; otherwise `gd-error` with `phase: "init"` and the dashboard doesn't render | | `"embeddedOnly"` | Skipped; a `gd-warning` is dispatched when the dashboard has any | Loaded | No | | `"backendOnly"` | Loaded | Ignored, with a `gd-warning` | Yes; otherwise `gd-error` with `phase: "init"` | | `"disabled"` | Skipped | Ignored, with a `gd-warning` | No | Set `pluginMode = "disabled"` to keep the no-plugin behavior of `gd-dashboard` without the warning. ### Methods | Method | Description | | --- | --- | | `refresh(): Promise` | Re-initializes the dashboard with the current `config` by dispatching the `initializeDashboard` command. Resolves on `GDC.DASH/EVT.INITIALIZED`; rejects, and dispatches `gd-error` with `phase: "refresh"`, on `GDC.DASH/EVT.COMMAND.FAILED`. | | `replaceFilters(filters: (IDashboardFilter \| FilterContextItem)[]): Promise` | Replaces the filter selection by dispatching `changeFilterContextSelection(filters, true)`; filters you leave out are reset. Resolves on `GDC.DASH/EVT.FILTER_CONTEXT.CHANGED`; rejects, and dispatches `gd-error` with `phase: "replaceFilters"`, on `GDC.DASH/EVT.COMMAND.FAILED`. This is the Web Components counterpart of the `setFilterContext` postMessage command used with iframe embedding. | Command rules: - Call a method only after `gd-ready`. Before that, it rejects with `Dashboard is not ready for commands yet.` and dispatches `gd-error` with `phase: "invalidUsage"` and `command` set to the method name. - Calling a method again while its previous call is in flight returns the same promise. - Removing the element, or a reload that recreates the dashboard runtime, rejects in-flight commands with `Dashboard runtime is no longer active.` ### Events | Event | `detail` | When | | --- | --- | --- | | `gd-ready` | `undefined` | The dashboard runtime accepts commands. Fires again whenever the runtime is recreated, for example after you assign a new `config` or `context`. | | `gd-warning` | `{ phase: "pluginMode", message, pluginMode, ignoredSource: "extraPlugins" \| "backendPlugins", dashboard? }` | Plugins were skipped; see [Dashboard plugins](#dashboard-plugins). The message is also logged with `console.warn`. | | `gd-error` | `{ phase, message, cause, command?, dashboard? }` | `phase` is `"init"` (dependencies, the context, or the dashboard failed to load), `"update"` (a re-render failed), `"invalidUsage"` (an identity change, or a command before `gd-ready`), `"refresh"`, or `"replaceFilters"` (the command failed). | | `GDC.DASH/EVT.*` | The event's `payload` | Every [dashboard event](https://www.gooddata.ai/docs/gooddata-ui/latest/dashboard-plugins/dashboard-events/index.md) is forwarded as a DOM event named after its type, for example `GDC.DASH/EVT.INITIALIZED`. | All events are `CustomEvent` instances that don't bubble and aren't cancelable. ```html ``` ## [`gd-insight-embed`](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/#gd-insight-embed) Renders [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/visualisations/insightview/index.md). ### Attributes | Attribute | Value | Description | | --- | --- | --- | | `insight` | Visualization ID | Required unless you set the `insight` property. Locked after the first render. | | `workspace` | Workspace ID | Overrides the workspace of the shared context. | | `locale` | Locale code such as `en-US` | Locale of the visualization UI. Attribute only; there is no `locale` property. | | `title` | Boolean attribute or string | Present without a value: shows the title stored with the visualization. With a string: shows that string instead. Absent: no title. | | `filters` | JSON array of filter objects | Parsed into [`IFilter[]`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-model/sdk-model.ifilter/index.md) and validated. An invalid value is logged with `console.error` and ignored. | | `mapbox` | Mapbox access token | Sets `config.mapboxToken`; overrides the context token. Ignored once you assign the `config` property. | | `agGrid` | AG Grid license key | Sets `config.agGridToken`; overrides the context token. Ignored once you assign the `config` property. | ### Properties | Property | Type | Description | | --- | --- | --- | | `context` | `{ backend, workspaceId?, mapboxToken?, agGridToken? }` | Per-element backend and workspace. Missing fields fall back to the shared context. | | `config` | The `config` prop of [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md) | Replaces the config built from the `mapbox` and `agGrid` attributes and the context tokens. | | `insight` | `string \| ObjRef` | Visualization ID or reference. Same identity rule as the attribute. | | `filters` | [`IFilter[]`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-model/sdk-model.ifilter/index.md) | Filters applied to the visualization. Replaces the `filters` attribute once assigned. | | `title` | `boolean \| string` | Same meaning as the attribute. | ### Methods | Method | Description | | --- | --- | | `refresh(): Promise` | Evicts the cached definition of this visualization from the bundle's [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md) cache and remounts the visualization, so it's fetched again. Resolves when loading finishes (`insightLoaded`, or `loadingChanged` with `isLoading: false`); rejects when the visualization reports `error` or when the cache eviction fails. If the workspace or the `insight` input can't be resolved yet, the eviction is skipped and the visualization is only remounted. Concurrent calls share one promise. | `refresh()` runs after the element has already rendered, so a stale visualization is briefly visible and fetched twice. When you control the mount timing, for example after a user edits a visualization and returns to a page of embeds, call [`clearCaches()`](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/index.md#module-exports) and await it before mounting the elements instead. ### Events | Event | `detail` | When | | --- | --- | --- | | `drill` | [`IDrillEvent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.idrillevent/index.md) | The user drills in the visualization. | | `error` | [`GoodDataSdkError`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.gooddatasdkerror/index.md) | Loading or computing the visualization failed. `gd-error` fires too. | | `exportReady` | [`IExportFunction`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.iexportfunction/index.md) | The export function is available. | | `loadingChanged` | `ILoadingState` | Loading started or finished. | | `insightLoaded` | [`IInsight`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-model/sdk-model.iinsight/index.md) | The visualization definition has loaded. | | `gd-ready` | `undefined` | Once, after the first successful render. | | `gd-error` | `{ phase, message, cause, insight? }` | `phase` is `"init"`, `"update"` (the visualization reported an error; `insight` holds its ID), or `"invalidUsage"`. | All events are `CustomEvent` instances that don't bubble and aren't cancelable. ```html ``` ## [Migrate from `gd-dashboard` and `gd-insight`](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/#migrate-from-gd-dashboard-and-gd-insight) 1. Rename the tags. The attributes are the same (`dashboard`, `workspace`, `locale`, `readonly`, `mapbox`, `agGrid`; `insight`, `workspace`, `locale`, `title`, `filters`, `mapbox`, `agGrid`), and existing listeners for `GDC.DASH/EVT.*`, `drill`, `error`, `exportReady`, `loadingChanged`, and `insightLoaded` keep working. 2. Add CSS. Give the embed elements `display: block` or `display: flex` and a height in your own stylesheet. 3. Stop switching the `dashboard` or `insight` attribute to show another object. Replace the element instead; the embed elements reject identity changes with `gd-error`. 4. Move runtime updates from attributes to properties: `el.filters = [...]`, `el.title = "..."`, `el.config = {...}`, and `await el.replaceFilters([...])` for dashboard filters. 5. Listen for `gd-error`. The legacy elements only log load failures to the console. 6. Decide on plugins. Without `moduleFederationIntegration`, `gd-dashboard-embed` warns that plugins linked on the server were skipped. Set `pluginMode = "disabled"` to keep the legacy behavior without the warning, or provide `moduleFederationIntegration` to load them. --- # Dashboard custom element > Embed a dashboard with gd-dashboard, configure its workspace, locale, and read-only mode, and subscribe to dashboard events. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/dashboard-custom-element/index.md > **Legacy element:** > `gd-dashboard` is the legacy dashboard element, kept for existing integrations. For new integrations, use `gd-dashboard-embed`, which adds JavaScript properties, the `refresh()` and `replaceFilters()` methods, dashboard plugin loading, and the `gd-ready` and `gd-error` events. See [Dashboard and visualization embed elements](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/index.md). `gd-dashboard` embeds a GoodData dashboard. It renders the [[`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component](https://www.gooddata.ai/docs/gooddata-ui/latest/dashboard-plugins/dashboard/index.md): ```html ``` ## [Supported attributes](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/dashboard-custom-element/#supported-attributes) ```html ``` - `dashboard` - required, the ID of the dashboard to embed. - `workspace` - optional, the ID of the workspace for this dashboard. By default, it's taken from the context, for example from the script URL. - `locale` - optional, defaults to `en-US`. The localization of the dashboard UI. Use the hyphenated form (`en-US`, `de-DE`); an unknown value falls back to `en-US`. For all available languages, see [the full list of available localizations][1]. - `readonly` - optional. If enabled, the dashboard is embedded in read-only mode and all user interaction that would alter any backend state is disabled (for example, creating or changing alerts and scheduled emails). - `mapbox` - optional, a Mapbox access token used by geo charts on the dashboard. Overrides the `mapboxToken` from the context. - `agGrid` - optional, an AG Grid license key used by pivot tables on the dashboard. Overrides the `agGridToken` from the context. You can also set the workspace ID once on the context instead of passing it to every element. See the code example in [Web Components authentication][2]. > **Note:** > The locale affects only the UI elements, not your data or the metadata language. ## [Supported events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/dashboard-custom-element/#supported-events) `gd-dashboard` forwards every event the [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component emits as a DOM event named after the event type (see [`DashboardEventType`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboardeventtype/index.md)), for example `GDC.DASH/EVT.INITIALIZED`. Events don't bubble and aren't cancelable. Subscribe to them as to any other DOM event: ```html ``` [1]: https://github.com/gooddata/gooddata-ui-sdk/blob/master/libs/sdk-model/src/base/localization.ts [2]: ../web-components-auth/ --- # Visualization custom element > Embed a saved visualization with the legacy gd-insight element, set its workspace, locale, title, filters, and size, and subscribe to its events. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/insight-custom-element/index.md > **Legacy element:** > `gd-insight` is the legacy visualization element, kept for existing integrations. For new integrations, use `gd-insight-embed`, which adds the `filters`, `title`, `config`, and `context` properties, the `refresh()` method, and the `gd-ready` and `gd-error` events. See [Dashboard and visualization embed elements](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/index.md). `gd-insight` embeds a single saved visualization. It renders the [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/visualisations/insightview/index.md) component: ```html ``` > **Give the element a height:** > The `gd-insight` element uses a flex layout that adjusts to the size of its container. If the container doesn't have `display: flex`, the element has zero height. You can also set the height explicitly with CSS, for example with the inline `style="height: 500px"`. > **Note:** > The term 'insight' is an obsolete expression for 'visualizations' but is still employed within the SDK's components and functions. ## [Supported attributes](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/insight-custom-element/#supported-attributes) ```html ``` - `insight` - required, the ID of the visualization to embed. - `workspace` - optional, the ID of the workspace of this visualization. By default, it's taken from the context, for example from the script URL. - `locale` - optional, defaults to `en-US`. The localization of the visualization UI. Use the hyphenated form (`en-US`, `de-DE`); an unknown value falls back to `en-US`. For all available languages, see [the full list of available localizations][1]. - `title` - optional. As a boolean attribute, it renders the title stored with the visualization. As a string, it renders that string instead. - `filters` - optional, a JSON array of filter objects applied to the visualization. An invalid value is logged with `console.error` and ignored. - `mapbox` - optional, a Mapbox access token used by geo charts. Overrides the `mapboxToken` from the context. - `agGrid` - optional, an AG Grid license key used by pivot tables. Overrides the `agGridToken` from the context. ```html ``` You can also set the workspace ID once on the context instead of passing it to every element. See the code example in [Web Components authentication][2]. > **Note:** > The locale affects only the UI elements, not your data or the metadata language. ## [Supported events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/insight-custom-element/#supported-events) `gd-insight` emits the following events. The `detail` of each event is the argument of the matching [[`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md) callback][3]: - `drill` - the user drilled in the visualization. The detail is the drill event, [`IDrillEvent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.idrillevent/index.md). - `error` - loading or computing the visualization failed. The detail is the [`GoodDataSdkError`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.gooddatasdkerror/index.md). - `exportReady` - the visualization is ready to be exported. The detail is the export function, [`IExportFunction`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.iexportfunction/index.md); call it to export the data. - `loadingChanged` - the loading state of the visualization changed. The detail is `ILoadingState`. - `insightLoaded` - the visualization definition has loaded. The detail is the [`IInsight`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-model/sdk-model.iinsight/index.md). Events don't bubble and aren't cancelable. Subscribe to them as to any other DOM event: ```html ``` [1]: https://github.com/gooddata/gooddata-ui-sdk/blob/master/libs/sdk-model/src/base/localization.ts [2]: ../web-components-auth/ [3]: ../../../visualisations/insightview/#props --- # AI Assistant custom element > Embed the AI Assistant with gd-ai-assistant, set its workspace and locale, subscribe to its chat events, and compose the chat from several elements. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/ai-assistant-custom-element/index.md `gd-ai-assistant` embeds the GoodData AI Assistant. It renders the [`GenAIAssistant`](https://www.gooddata.ai/docs/gooddata-ui/latest/ai-modules/ai-assistant/index.md) component: ```html ``` ## [Supported attributes](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/ai-assistant-custom-element/#supported-attributes) ```html ``` - `workspace` - optional, the ID of the workspace for this AI Assistant. By default, it's taken from the context, for example from the script URL. - `locale` - optional, defaults to `en-US`. The localization of the AI Assistant interface. For all available languages, see [the full list of available localizations][1]. - `objectTypes` - optional, a JSON array of metadata object types that the assistant searches and suggests, for example `'["dashboard","visualization"]'`. Valid values are `dataset`, `attribute`, `label`, `fact`, `date`, `metric`, `visualization`, and `dashboard` (see [`GenAIObjectType`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-model/sdk-model.genaiobjecttype/index.md)). If omitted, the assistant uses its default set of object types. You can also set the workspace ID once on the context instead of passing it to every element. See the code example in [Web Components authentication][2]. > **Note:** > The locale affects only the UI elements, not your data or the metadata language. ## [Supported events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/ai-assistant-custom-element/#supported-events) `gd-ai-assistant` emits `linkClick` and forwards every chat event of the [`GenAIAssistant`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-gen-ai/sdk-ui-gen-ai.genaiassistant/index.md) component (see [`ChatEvent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-gen-ai/sdk-ui-gen-ai.chatevent/index.md)) as a DOM event named after the event type. The most common ones are: - `linkClick` - when a user clicks on a link in the chat. - `chatOpened` / `chatClosed` - when the chat is opened or closed. - `chatReset` - when the chat is reset. - `chatUserMessage` - when a user message is added to the chat. - `chatAssistantMessage` - when an assistant message is added to the chat. - `chatFeedback` - when a user provides feedback on the assistant message. - `chatVisualizationError` - when an error occurs while rendering the visualization in the chat. - `chatSaveVisualizationSuccess` / `chatSaveVisualizationError` - when saving a visualization from the chat succeeds or fails. - `chatCopyToClipboard` - when a user copies content to the clipboard. Events **do not bubble** and **are not cancelable**. Here is how you can subscribe to one from your code: ```html ``` ## [Compose the chat from several elements](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/ai-assistant-custom-element/#compose-the-chat-from-several-elements) The library registers three more elements that share the AI Assistant's code: | Element | Renders | Use it to | | --- | --- | --- | | `gd-ai-chat` | The same [`GenAIAssistant`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-gen-ai/sdk-ui-gen-ai.genaiassistant/index.md) component as `gd-ai-assistant` | Alias of `gd-ai-assistant`; the two tags behave identically. | | `gd-ai-conversations` | The [`GenAIConversations`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-gen-ai/sdk-ui-gen-ai.genaiconversations/index.md) component: the list of the user's conversations | Show the conversation list separately from the chat. Selecting a conversation makes it the current one in the shared store. | | `gd-ai-provider` | Nothing visible; it owns the chat store | Make the `gd-ai-assistant`, `gd-ai-chat`, and `gd-ai-conversations` elements placed inside it share one conversation state. | `gd-ai-chat` and `gd-ai-conversations` support the same `workspace`, `locale`, and `objectTypes` attributes as `gd-ai-assistant` and emit the same events. ### [Share one store with `gd-ai-provider`](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/ai-assistant-custom-element/#share-one-store-with-gd-ai-provider) Wrap the chat and the conversation list in `gd-ai-provider`. When the provider connects and the shared context is available, it creates the chat store, and every `gd-ai-assistant`, `gd-ai-chat`, or `gd-ai-conversations` element inside it connects to that store instead of creating its own: ```html
``` | Attribute | Description | | --- | --- | | `workspace`, `locale`, `objectTypes` | Applied to the shared store. Same values as on `gd-ai-assistant`. | | `store` | Optional ID of the store. The provider generates one when the attribute is absent. Child elements look up the nearest ancestor with a `store` attribute, so the provider doesn't have to be their direct parent. | The provider reads its attributes once, when it connects; changing them later has no effect. The provider itself dispatches no DOM events; listen on the `gd-ai-assistant` and `gd-ai-conversations` elements. ### [JavaScript properties and methods](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/ai-assistant-custom-element/#javascript-properties-and-methods) `gd-ai-assistant`, `gd-ai-chat`, `gd-ai-conversations`, and `gd-ai-provider` expose these JavaScript properties. Assign them after the element is defined, for example after `await customElements.whenDefined("gd-ai-provider")`. | Property or method | Description | | --- | --- | | `onLinkClick` | Callback invoked when the user clicks a link in the chat, with a `CustomEvent` whose `detail` is the `linkClick` payload described below. The element always suppresses the chat's own navigation and dispatches the `linkClick` event before it calls the callback. | | `onDispatcher` | Callback that receives the store's `dispatch` function once the store exists. Use it to dispatch the actions exported by `@gooddata/sdk-ui-gen-ai`. | | `startNewConversationAction()` | Dispatches the start-new-conversation action to the store. The method is attached asynchronously after the element connects, so guard early calls with `?.`. | On `gd-ai-provider`, assigning `onLinkClick` or `onDispatcher` forwards the callback to the shared store, and `startNewConversationAction()` acts on the shared store. ```html ``` The `linkClick` event `detail` carries `type` (the linked object type, `"setting"`, or `"report"`), `id`, `workspaceId`, `itemUrl`, `newTab`, and `action` (`"open"` or `"copy"`), and, depending on the link, `section`, `dashboard`, `insights`, `dashboardStatus`, `visualization`, and `visualizationStatus`. [1]: https://github.com/gooddata/gooddata-ui-sdk/blob/master/libs/sdk-model/src/base/localization.ts [2]: ../web-components-auth/ --- # Authenticate Web Components > Authenticate Web Components against GoodData using the auth=sso script parameter for automatic SSO or setContext for a programmatic flow. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-auth/index.md The Web Components library can sign the user in to GoodData automatically when you add the `auth` query parameter to the script URL. For more control over the flow, [authenticate the user programmatically](#programmatic-authentication) instead. The only supported value of the `auth` parameter is `sso`. With it, the script checks whether the user is signed in and, if not, redirects the user to the sign-in page of your identity provider. See [Automatic authentication](#automatic-authentication). Without the `auth` parameter, or with any other value, the library doesn't sign the user in and expects you to set up [programmatic authentication](#programmatic-authentication). Until you call `setContext`, the custom elements on the page render nothing or a loading indicator, and after five seconds the library logs a console warning that no authentication context was provided. > **Note:** > All your users must have a GoodData account and have access to your GoodData workspace. ## [Programmatic authentication](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-auth/#programmatic-authentication) To customize the authentication flow, create the authenticated backend yourself. Next to the library, your GoodData instance serves `tigerBackend.js`, which exports the backend factory for GoodData Cloud and GoodData.CN (code name `tiger`). A custom authentication setup looks like this: ```html ``` See [Authenticate to the backend](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/authentication/index.md) for the other provider options, including a JWT from your own portal and API tokens (development only). `tigerBackend.js` exports everything from `@gooddata/sdk-backend-tiger`, so every provider on that page is available here. The `gd-dashboard-embed` and `gd-insight-embed` elements also accept a per-element `context` property with the same `{ backend, workspaceId }` shape, which overrides the shared context for that element only. See [Dashboard and visualization embed elements](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/index.md#how-inputs-resolve). ## [Automatic authentication](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-auth/#automatic-authentication) Add the `?auth=sso` parameter to the script URL to sign users in automatically on GoodData Cloud and GoodData.CN: ```html ``` This is equivalent to the following programmatic setup: ```html ``` Whenever the user isn't signed in to GoodData, the library redirects the browser window to the identity provider configured in GoodData. After the user signs in, the identity provider redirects the window back to the page the user started on. If your organization uses Federated Identity Management, pass the user's identity provider ID to the redirect: replace `redirectToTigerAuthentication` with the handler that `createRedirectToTigerAuthenticationWithParams({ externalProviderId })` returns, imported from the same `tigerBackend.js`. See [Pick the identity provider](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/authentication/index.md#pick-the-identity-provider). --- # Framework guides > Use GoodData.UI in Angular, Next.js, and Vue apps — framework-specific setup via React components or the Web Components wrapper. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/index.md GoodData.UI ships as React components, but you don't need a React app to use it. These guides add GoodData to other frameworks, either by wrapping the React components or by embedding the framework-agnostic [Web Components](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/index.md). Any JavaScript or TypeScript framework can embed GoodData through Web Components. For SvelteKit, see the [frontend integration series blog post](https://www.gooddata.ai/blog/frontend-integration-series-sveltekit/). --- # Embed GoodData in an Angular app > Embed GoodData dashboards, visualizations, and the AI Assistant in an Angular app with the Web Components library, custom element schemas, and event listeners. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/angular-integration/index.md Angular works well with Web Components and custom elements. This guide embeds a GoodData dashboard, a visualization, and the AI Assistant in an Angular app with the `gd-dashboard-embed`, `gd-insight-embed`, and `gd-ai-assistant` custom elements. It's based on a project generated with `ng new my-app`, with standalone components; adapt the file names to your project. ## [Load the Web Components library](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/angular-integration/#load-the-web-components-library) Add a ` ``` The `?auth=sso` parameter signs the user in automatically. For a programmatic flow, such as an API token or your own [`NotAuthenticatedHandler`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.notauthenticatedhandler/index.md), see [Web Components authentication](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-auth/index.md). The [Web Components overview](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/index.md) explains the script URL. ## [Allow custom elements](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/angular-integration/#allow-custom-elements) Add `CUSTOM_ELEMENTS_SCHEMA` to the component that uses the GoodData elements. Otherwise, Angular reports them as unknown elements: ```diff - import { Component } from "@angular/core"; + import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core"; @Component({ selector: "app-root", templateUrl: "./app.component.html", + schemas: [CUSTOM_ELEMENTS_SCHEMA], }) export class AppComponent {} ``` If your app still uses `NgModule`, add `schemas: [CUSTOM_ELEMENTS_SCHEMA]` to the module instead. ## [Embed a dashboard and a visualization](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/angular-integration/#embed-a-dashboard-and-a-visualization) Use the elements anywhere in the component's template, `src/app/app.component.html`: ```html ``` Copy the visualization ID and the dashboard ID from the browser's address bar, on the Analyze and Dashboards pages. Give `gd-insight-embed` `display: block` and a fixed height; otherwise it collapses to zero height. [Dashboard and visualization embed elements](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/index.md) lists every attribute, property, method, and event of the two embed elements. ## [Set the IDs from component code](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/angular-integration/#set-the-ids-from-component-code) Bind the attributes to component fields: ```ts // src/app/app.component.ts import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core"; @Component({ selector: "app-root", templateUrl: "./app.component.html", schemas: [CUSTOM_ELEMENTS_SCHEMA], }) export class AppComponent { dashboardId = ""; visualizationId = ""; workspaceId = ""; } ``` ```html ``` The embed elements lock the `dashboard` and `insight` identity after the first render. To show a different dashboard or visualization, render a new element, for example with `@if` or `@for` keyed on the ID, instead of changing the bound value. ## [Listen to events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/angular-integration/#listen-to-events) All three elements dispatch DOM custom events. ### [Visualization events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/angular-integration/#visualization-events) Event names without dots or slashes work with Angular event binding. Add a handler to the component: ```ts onInsightLoaded(event: Event) { console.log((event as CustomEvent).detail); } ``` and bind it in the template: ```html ``` See the [`gd-insight-embed` events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/index.md#events-1). ### [Dashboard events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/angular-integration/#dashboard-events) Dashboard event names such as `GDC.DASH/EVT.INITIALIZED` contain characters Angular event binding doesn't accept, so add the listeners on the DOM element. Mark the element with a template reference variable: ```html ``` Then get the element in the component, attach the listener, and remove it when the component is destroyed: ```ts // src/app/app.component.ts import { Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, OnDestroy, ViewChild } from "@angular/core"; const DASHBOARD_EVENT = "GDC.DASH/EVT.INITIALIZED"; @Component({ selector: "app-root", templateUrl: "./app.component.html", schemas: [CUSTOM_ELEMENTS_SCHEMA], }) export class AppComponent implements OnDestroy { dashboardId = ""; private dashboardElement?: HTMLElement; private onDashboardInitialized = (event: Event) => { console.log((event as CustomEvent).detail); }; @ViewChild("dashboard") set dashboard(ref: ElementRef) { this.dashboardElement = ref.nativeElement; this.dashboardElement.addEventListener(DASHBOARD_EVENT, this.onDashboardInitialized); } ngOnDestroy() { this.dashboardElement?.removeEventListener(DASHBOARD_EVENT, this.onDashboardInitialized); } } ``` `gd-dashboard-embed` also dispatches `gd-ready` and `gd-error`. See the [`gd-dashboard-embed` events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/index.md#events). ### [AI Assistant events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/angular-integration/#ai-assistant-events) Attach `gd-ai-assistant` listeners the same way. Mark the element: ```html ``` and add the listener in the component: ```ts private onLinkClick = (event: Event) => { console.log((event as CustomEvent).detail); }; @ViewChild("aiAssistant") set aiAssistant(ref: ElementRef) { ref.nativeElement.addEventListener("linkClick", this.onLinkClick); } ``` Remove the listener in `ngOnDestroy`, as for the dashboard. See the [AI Assistant events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/ai-assistant-custom-element/index.md#supported-events). --- # Render GoodData.UI React components in Angular > Wrap GoodData.UI React components in Angular components with createRoot, import their styles, and bind their props from Angular templates. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/angular-react-integration/index.md To use the GoodData.UI React components in an Angular app, wrap each one in an Angular component that renders it with React's `createRoot` API. GoodData.UI needs TypeScript 5.8.3 or newer, which Angular supports from version 19.2. See [Platform compatibility](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/platform-compatibility/index.md) for the rest of the toolchain. > **Consider Web Components:** > Depending on your use case, it may be easier to embed dashboards and visualizations with the GoodData [Web Components library](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/angular-integration/index.md) than to wrap React components yourself. ## [Install the packages](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/angular-react-integration/#install-the-packages) Install React and the GoodData.UI packages. GoodData.UI works with React 18 and 19: ```bash npm install react react-dom @gooddata/sdk-ui-all @gooddata/sdk-backend-tiger npm install --save-dev @types/react @types/react-dom ``` `@gooddata/sdk-ui-all` re-exports the model, core UI, charts, geo, pivot, filters, and [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md) packages. It doesn't include `@gooddata/sdk-ui-dashboard` or the AI packages; install those separately if you need them. ## [Update the global configuration](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/angular-react-integration/#update-the-global-configuration) 1. Add `(window as any).global = window;` to `polyfills.ts` to provide the missing `global`. 2. Add `"skipLibCheck": true` to your `tsconfig.json` to avoid misleading errors during compilation. ## [Declare the Angular wrapper component](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/angular-react-integration/#declare-the-angular-wrapper-component) The wrapper renders a React component into its own DOM node and renders it again when an input changes. You can wrap each GoodData.UI component separately, or write one generic wrapper that takes the React component as an input. The following example wraps the [`Kpi`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.kpi/index.md) component. It creates the backend with [`ContextDeferredAuthProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-tiger/sdk-backend-tiger.contextdeferredauthprovider/index.md), which uses the GoodData session cookie and redirects to the GoodData sign-in when the session is missing. See [Authenticate to the backend](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/authentication/index.md) for the other options. ```ts // kpi.component.ts import { createElement } from "react"; import { createRoot, type Root } from "react-dom/client"; import { AfterViewInit, Component, Input, OnChanges, OnDestroy } from "@angular/core"; import { type IFilter, type IKpiProps, Kpi, newMeasure } from "@gooddata/sdk-ui-all"; import { ContextDeferredAuthProvider, redirectToTigerAuthentication, tigerFactory, } from "@gooddata/sdk-backend-tiger"; // For illustration only: create the backend once in your app and import it here. const backend = tigerFactory() .onHostname("https://example.gooddata.com") .withAuthentication(new ContextDeferredAuthProvider(redirectToTigerAuthentication)); @Component({ selector: "app-kpi", template: '', }) export class KpiComponent implements OnChanges, AfterViewInit, OnDestroy { @Input() measureId = ""; @Input() format?: string; @Input() workspace = ""; @Input() filters: IFilter[] = []; public readonly rootDomId = crypto.randomUUID(); private root: Root | null = null; private getRootDomNode(): HTMLElement { const node = document.getElementById(this.rootDomId); if (!node) { throw new Error(`Node '${this.rootDomId}' not found.`); } return node; } private getProps(): IKpiProps { const { workspace, measureId, filters, format } = this; return { backend, workspace, measure: newMeasure(measureId, (m) => (format ? m.format(format) : m)), filters, }; } private render(): void { if (!this.root && document.getElementById(this.rootDomId)) { this.root = createRoot(this.getRootDomNode()); } this.root?.render(createElement(Kpi, this.getProps())); } ngOnChanges(): void { this.render(); } ngAfterViewInit(): void { this.render(); } ngOnDestroy(): void { this.root?.unmount(); this.root = null; } } ``` ## [Render charts](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/angular-react-integration/#render-charts) Charts fill their container, so give the wrapper's root DOM node a size: ```ts // column-chart.component.ts @Component({ selector: "app-column-chart", template: '
', }) export class ColumnChartComponent { // Same structure as KpiComponent, rendering ColumnChart from @gooddata/sdk-ui-all. } ``` Import the chart styles into your global styles, either in `styles.css`: ```css @import "@gooddata/sdk-ui-charts/styles/css/main.css"; ``` or in `angular.json`: ```json { "architect": { "build": { "options": { "styles": ["src/styles.css", "node_modules/@gooddata/sdk-ui-charts/styles/css/main.css"] } } } } ``` If you use the pivot table, also import `@gooddata/sdk-ui-pivot/styles/css/main.css`. For more about global styles, see the [Angular workspace configuration](https://angular.dev/reference/configs/workspace-config#styles-and-scripts-configuration). ## [Use the component](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/angular-react-integration/#use-the-component) Use the wrapped component in your templates and bind its inputs: ```html ``` To handle the loading and error states yourself, pass components that render nothing in `getProps`: ```ts return { // ...the props above LoadingComponent: () => null, ErrorComponent: () => null, }; ``` See [LoadingComponent](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/loading-component/index.md) and [ErrorComponent](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/error-component/index.md). --- # Add GoodData.UI to a Next.js app > Render GoodData.UI charts in a Next.js App Router application with a client-side backend, the generated catalog, and the GoodData session cookie. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/nextjs-integration/index.md GoodData.UI components are React client components, so in a Next.js app they render on the client. This guide adds them to a Next.js App Router project: it installs the packages, generates the catalog of measures and attributes, creates the backend, and renders a bar chart. ## [Install the packages](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/nextjs-integration/#install-the-packages) Install the backend connector, the core UI package, the data model, and the charts: ```bash npm install @gooddata/sdk-backend-tiger @gooddata/sdk-ui @gooddata/sdk-model @gooddata/sdk-ui-charts ``` [Installation](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/installation/index.md#install-the-packages) lists the other component packages. `@gooddata/sdk-ui-all` re-exports the model, core UI, charts, geo, pivot, filters, and [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md) packages in one dependency, but not `@gooddata/sdk-ui-dashboard` or the AI packages. Add the catalog-export tool as a dev dependency. It generates a TypeScript file with a constant for every measure, attribute, and fact in your workspace: ```bash npm install --save-dev @gooddata/catalog-export ``` ## [Configure catalog-export](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/nextjs-integration/#configure-catalog-export) Add a `gooddata` block and a `refresh-md` script to `package.json`: ```json { "gooddata": { "hostname": "https://", "workspaceId": "", "catalogOutput": "app/catalog.ts" }, "scripts": { "refresh-md": "gdc-catalog-export" } } ``` ## [Export the catalog](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/nextjs-integration/#export-the-catalog) `catalog-export` reads an API token from the `TIGER_API_TOKEN` environment variable or from a `.env` file in the project root. See [Create an API token](https://www.gooddata.ai/docs/cloud/manage-organization/user-token/). Keep `.env` out of version control: ```bash echo "TIGER_API_TOKEN=" >> .env npm run refresh-md ``` The command writes the constants to `app/catalog.ts`. Run it again whenever the workspace's logical data model (LDM) changes. The token stays on your machine: Next.js exposes only variables prefixed with `NEXT_PUBLIC_` to client code, so `TIGER_API_TOKEN` never reaches the browser bundle. ## [Allow your app's origin](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/nextjs-integration/#allow-your-apps-origin) The Next.js app runs on a different origin than GoodData, so add its origin, for example `https://app.example.com` or `http://localhost:3000`, to your organization's allowed origins. See [Allow your origin in production](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/cross-origin-issues/index.md#allow-your-origin-in-production). ## [Create the backend](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/nextjs-integration/#create-the-backend) Create the backend in a client module. [`ContextDeferredAuthProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-tiger/sdk-backend-tiger.contextdeferredauthprovider/index.md) uses the session cookie the browser gets when the user signs in to GoodData, and `redirectToTigerAuthentication` sends the user to the GoodData sign-in when the session is missing: ```ts // app/backend.ts "use client"; import { ContextDeferredAuthProvider, redirectToTigerAuthentication, tigerFactory, } from "@gooddata/sdk-backend-tiger"; export const backend = tigerFactory() .onHostname("https://") .withAuthentication(new ContextDeferredAuthProvider(redirectToTigerAuthentication)); ``` Don't put an API token into a `NEXT_PUBLIC_` variable for a production build: the value is written into the JavaScript bundle, and anyone who loads the page can reuse it. If the browser blocks the GoodData cookie because your app runs on another site, or your own portal signs users in, use a JSON Web Token instead. [Authenticate to the backend](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/authentication/index.md) covers both cases. ## [Import the styles](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/nextjs-integration/#import-the-styles) Import the stylesheets of the packages you installed in `app/layout.tsx`: ```tsx // app/layout.tsx import "@gooddata/sdk-ui-kit/styles/css/main.css"; import "@gooddata/sdk-ui-charts/styles/css/main.css"; import type { ReactNode } from "react"; export default function RootLayout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ## [Render a chart](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/nextjs-integration/#render-a-chart) Put the providers and the chart in a client component: ```tsx // app/gooddata-chart.tsx "use client"; import { BackendProvider, WorkspaceProvider } from "@gooddata/sdk-ui"; import { BarChart } from "@gooddata/sdk-ui-charts"; import { backend } from "./backend.js"; import * as Md from "./catalog.js"; export function GoodDataChart() { return (
); } ``` The constant names come from your workspace, so `Md.YourFact` and `Md.YourAttribute` stand in for whatever `catalog-export` generated. Charts fill their container, so give the wrapper a height. Render the component from a page: ```tsx // app/page.tsx import { GoodDataChart } from "./gooddata-chart.js"; export default function Page() { return ; } ``` ## [Run the application](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/nextjs-integration/#run-the-application) Start the development server and open the page: ```bash npm run dev ``` If the chart stays empty and the browser console shows a CORS error, check the allowed origins. If it shows [`NotAuthenticated`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.notauthenticated/index.md), check the authentication setup in [Authenticate to the backend](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/authentication/index.md#troubleshooting). --- # Embed GoodData in a Vue app > Embed GoodData dashboards, visualizations, and the AI Assistant in a Vue app with the Web Components library, the Vite custom-element setting, and event listeners. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/vue-integration/index.md Vue supports Web Components out of the box. This guide embeds a GoodData dashboard, a visualization, and the AI Assistant in a Vue app built with Vite, using the `gd-dashboard-embed`, `gd-insight-embed`, and `gd-ai-assistant` custom elements. ## [Load the Web Components library](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/vue-integration/#load-the-web-components-library) Add a `
``` Read the [prerequisites and limitations](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-overview/index.md#prerequisites-and-limitations) in the Web Components overview first. The `?auth=sso` parameter signs the user in automatically. For a programmatic flow, such as an API token or your own [`NotAuthenticatedHandler`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.notauthenticatedhandler/index.md), see [Web Components authentication](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/web-components-auth/index.md). ## [Configure custom elements](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/vue-integration/#configure-custom-elements) Tell Vue which elements are Web Components. Otherwise, Vue looks for Vue components with those tag names and reports an error when it can't find them. Update `vite.config.js`: ```diff import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' // https://vite.dev/config/ export default defineConfig({ - plugins: [vue(), vueJsx()], + plugins: [vue({ + template: { + compilerOptions: { + isCustomElement: tag => tag.startsWith('gd-') + } + } + }), vueJsx()], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } } }) ``` ## [Embed a dashboard and a visualization](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/vue-integration/#embed-a-dashboard-and-a-visualization) Use the elements anywhere in a Vue template, for example in `src/App.vue`: ```html ``` Copy the visualization ID and the dashboard ID from the browser's address bar, on the Analyze and Dashboards pages. Give `gd-insight-embed` `display: block` and a fixed height; otherwise it collapses to zero height. [Dashboard and visualization embed elements](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/index.md) lists every attribute, property, method, and event of the two embed elements. ## [Set the IDs from component code](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/vue-integration/#set-the-ids-from-component-code) Bind the IDs from the component's script: ```diff ``` The embed elements lock the `dashboard` and `insight` identity after the first render. To show a different dashboard or visualization, render a new element, for example with a `key` bound to the ID, instead of changing the bound value. ## [Listen to events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/vue-integration/#listen-to-events) All three elements dispatch DOM custom events. ### [Visualization events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/vue-integration/#visualization-events) Listen to `gd-insight-embed` events with Vue event binding: ```diff ``` See the [`gd-insight-embed` events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/index.md#events-1). ### [Dashboard events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/vue-integration/#dashboard-events) Dashboard event names such as `GDC.DASH/EVT.INITIALIZED` contain characters Vue event binding doesn't accept, so add the listeners on the DOM element through a template ref, and remove them when the component unmounts: ```diff ``` `gd-dashboard-embed` also dispatches `gd-ready` and `gd-error`. See the [`gd-dashboard-embed` events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/embed-custom-elements/index.md#events). ### [AI Assistant events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/framework-integrations/vue-integration/#ai-assistant-events) Attach `gd-ai-assistant` listeners through a template ref too: ```diff ``` See the [AI Assistant events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/web-components/ai-assistant-custom-element/index.md#supported-events). --- # Interactivity > Handle user interactions with visualizations, from drill events to loading and error states. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/index.md These pages cover how a visualization behaves once it's on the page: what happens when someone clicks into a data point, how you find out that an export or a legend finished rendering, and how you swap in your own loading and error UI. Each topic maps to a specific callback or component prop, so pick the one that matches the behavior you need to hook into. This is a different concern from choosing *what data* a visualization shows in the first place — for attribute, date, measure-value, and ranking filters, see [Filters](https://www.gooddata.ai/docs/gooddata-ui/latest/filters/index.md). --- # Make visualizations drillable > Configure drillableItems with header predicates to make visualization measures or attributes interactive and receive drill event context. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/drillable-items/index.md Drilling is "zooming in" on a single measure value: filtering out all other data and breaking that value down by a new attribute of interest. To make a visualization drillable, pass at least one predicate function in the `drillableItems` prop. Clicks on the matching items then call your [`onDrill`](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/on-drill/index.md) callback. Drillable items can consist of the following entities: - Measures - Attribute display forms (labels), defined by their identifier A display form, or attribute label, is a different way of representing an attribute. For example, the `Name` attribute might have labels for `Firstname` and `Lastname`. See [Attributes](https://www.gooddata.ai/docs/cloud/model-data/concepts/attribute/). - Attribute values Visualization points that intersect any defined measures, attributes, or attribute values become drillable and emit events when the user clicks them. Ad-hoc measures (measures created from attributes or other measures, for example with `computeRatio`) are matched by the identifier they use in the execution. To make one drillable, pass the same identifier you used to build it. ## [Structure](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/drillable-items/#structure) ```ts drillableItems: [ (header: IMappingHeader, context: IMappingHeaderContext) => boolean, // Type: IHeaderPredicate ... ] ``` [`IHeaderPredicate`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.iheaderpredicate/index.md) defines the function that accepts [`IMappingHeader`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.imappingheader/index.md) and `context` as its parameters and returns a boolean value. This function is executed against every measure and attribute in a visualization. If the function returns `true`, the measure or attribute is drillable. If the function returns `false`, the measure or attribute is not drillable. You can write any logic to decide whether a measure or attribute is drillable, though you rarely need to. See [Predicates](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/predicates/index.md). ### [Predicate factory helpers](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/drillable-items/#predicate-factory-helpers) [`HeaderPredicates`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.headerpredicates/index.md) from `@gooddata/sdk-ui` builds the predicate functions for the most common drilling use cases. [`HeaderPredicates`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.headerpredicates/index.md) provides the following predicate factory functions: - `identifierMatch("")` The helper builds a predicate function that matches any measure or attribute in a visualization to the provided identifier. - `composedFromIdentifier("")` The helper builds a predicate function that matches any arithmetic measure in a visualization containing measures to the provided identifier in its tree of measures that the arithmetic measure is built from. > **Legacy URI helpers:** > `uriMatch` and `composedFromUri` match objects referenced by URI, which only GoodData Legacy uses. On GoodData Cloud and GoodData.CN, use the identifier helpers. ## [Set up drilling](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/drillable-items/#set-up-drilling) Pass an array of [`IHeaderPredicate`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.iheaderpredicate/index.md) functions in the `drillableItems` prop. The measures and attributes they match are highlighted and become drillable. This example makes the measure with the identifier `revenue` and the attribute label with the identifier `region` drillable: ```tsx import { HeaderPredicates, type IDrillEvent } from "@gooddata/sdk-ui"; import { InsightView } from "@gooddata/sdk-ui-ext"; function onDrillHandler(event: IDrillEvent) { // handle drill } export function RevenueVisualization() { return ( ); } ``` This example makes every arithmetic measure drillable whose tree of source measures contains the measure with the identifier `revenue`: ```tsx ``` Each event contains an object consisting of `dataView` and `drillContext`. `dataView` contains the underlying data used to render the chart from which the drill event originates. `drillContext` contains full context of which element the user clicked. ### [Use the generated catalog](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/drillable-items/#use-the-generated-catalog) With the catalog that [catalog-export](https://www.gooddata.ai/docs/gooddata-ui/latest/data-layer/data-model/export-catalog/index.md) generates, pass catalog constants to `HeaderPredicates.objMatch`, so you don't need to look up identifiers. `objMatch` first compares the `localIdentifier` of the measure or attribute. If that doesn't match, it compares the `objRef`, and then every instance of that measure or attribute becomes drillable. ```tsx import { HeaderPredicates } from "@gooddata/sdk-ui"; import * as Md from "./catalog.js"; ; ``` For drilling in embedded GoodData Cloud dashboards, see [Embedding and drilling in GoodData Cloud](https://www.gooddata.ai/docs/cloud/embed-visualizations/). --- # Header predicates > Match visualization headers for drilling with custom predicate functions or the HeaderPredicates helpers. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/predicates/index.md A header predicate matches elements of a visualization, such as a measure header item or an attribute header item, with logic as simple or complex as you need. [Drilling](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/drillable-items/index.md) uses predicates to decide which parts of a visualization the user can drill into. ## [Structure](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/predicates/#structure) A header predicate ([`IHeaderPredicate`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.iheaderpredicate/index.md)) is a function that takes two arguments and returns a boolean: ```ts type IHeaderPredicate = (header: IMappingHeader, context: IHeaderPredicateContext) => boolean; ``` - `header` ([`IMappingHeader`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.imappingheader/index.md)) is an object that describes the item whose match is being tested: an attribute descriptor, an attribute result header, a measure descriptor, a total descriptor, or a color descriptor. - `context` ([`IHeaderPredicateContext`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.iheaderpredicatecontext/index.md)) is additional data that describes the context in which the match is being tested. Its `dv` property holds the [`DataViewFacade`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.dataviewfacade/index.md) of the data view that produced the header passed as the first argument. If the predicate returns `true` for an item, the item is matched. The most common predicates are predefined in [`HeaderPredicates`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.headerpredicates/index.md), exported from `@gooddata/sdk-ui`: - `identifierMatch(identifier, type?)` matches an attribute or measure header by identifier, including measures derived from a master measure with that identifier. `uriMatch(uri)` does the same by URI, which only GoodData Legacy uses. - `localIdentifierMatch(localIdOrMeasure)` matches a header by the local identifier of its attribute or measure. - `objRefMatch(objRef)` and `objMatch(obj)` match a header by an [`ObjRef`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-model/sdk-model.objref/index.md), or by an attribute or simple measure object from the generated catalog. - `attributeItemNameMatch(name)` matches an attribute result header by its displayed value. - `composedFromIdentifier(identifier, type?)` matches arithmetic measures that use a measure with that identifier as an operand. `composedFromUri(uri)` does the same by URI. ## [Example](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/predicates/#example) You can also write your own predicates. This one matches all attribute values that start with a capital `A`: ```ts import { type IHeaderPredicate } from "@gooddata/sdk-ui"; import { isResultAttributeHeader } from "@gooddata/sdk-model"; const startsWithA: IHeaderPredicate = (header) => { return isResultAttributeHeader(header) && !!header.attributeHeaderItem.name?.startsWith("A"); }; ``` This one matches only attribute headers, and only when the visualization has at most three attributes: ```ts import { type IHeaderPredicate } from "@gooddata/sdk-ui"; import { isAttributeDescriptor } from "@gooddata/sdk-model"; const attributesIfNoMoreThanThree: IHeaderPredicate = (header, context) => { if (context.dv.def().attributes().length > 3) { return false; } return isAttributeDescriptor(header); }; ``` --- # onDrill > Handle drill events with the onDrill callback when users click drillable items in InsightView and the chart components. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/on-drill/index.md The `onDrill` callback runs when the user clicks a [drillable item](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/drillable-items/index.md) in a visualization. [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md), the chart components, and the pivot table accept it. Use it to respond to the click in your app, for example to open a detail view filtered by the clicked value. ## [Structure](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/on-drill/#structure) ```ts onDrill?: (event: IDrillEvent) => void | boolean; ``` The [`IDrillEvent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.idrillevent/index.md) holds: - `dataView`: the data the visualization was rendered from. - `drillContext`: what the user clicked, including the type of the clicked element and the `intersection` of measures and attribute values at that point. ## [Example](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/on-drill/#example) ```tsx import { HeaderPredicates, type IDrillEvent } from "@gooddata/sdk-ui"; import { InsightView } from "@gooddata/sdk-ui-ext"; function handleDrill(event: IDrillEvent) { console.log(event.drillContext.intersection); } export function DrillableVisualization() { return ( ")]} onDrill={handleDrill} /> ); } ``` ## [Notes](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/on-drill/#notes) - After `onDrill` runs, the visualization also dispatches a `drill` DOM event that bubbles up from the visualization's element, with the same [`IDrillEvent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.idrillevent/index.md) as its `detail`. Return `false` from `onDrill` to stop that DOM event; any other return value lets it through. - Only items that match `drillableItems` are drillable. Without `drillableItems`, `onDrill` never runs. See [Make visualizations drillable](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/drillable-items/index.md). --- # onExportReady > Capture the export function through onExportReady, configure CSV, XLSX, or PDF output, and call it to obtain the exported file. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/on-export-ready/index.md The `onExportReady` callback hands you an export function ([`IExportFunction`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.iexportfunction/index.md)) that exports the data of a rendered visualization into CSV, XLSX, or PDF. The function accepts one parameter, the `exportConfig` object ([`IExtendedExportConfig`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.iextendedexportconfig/index.md)), and resolves to an `IExportResult` that describes the exported file. The `onExportReady` callback is available on all visualization components, including [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md). ## [Structure](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/on-export-ready/#structure) ```tsx import { InsightView } from "@gooddata/sdk-ui-ext"; { /* hold onto the function and call it to export as needed */ }} />; ``` ### [exportConfig](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/on-export-ready/#exportconfig) The `exportConfig` object includes the following properties: - `format` (optional, string) specifies the format of the exported data. Can be set to `csv` (default), `xlsx`, `raw`, or `pdf`. - `title` (optional, string) becomes the file name of the export. When you omit it, [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md) uses the title of the saved visualization; other components use their `exportTitle` prop, then `Untitled`. Characters that aren't allowed in file names are stripped. - `delimiter` (only when `format` is `csv`; optional, string) is the field delimiter. - `showFilters` (only when `format` is `xlsx` or `pdf`; optional, boolean) specifies whether the applied filters are included in the export as human-readable context (`true`) or not (`false`; default). For PDF, this requires `visualizationObjectId`. - `includeFilterContext` (optional, boolean) is an alias of `showFilters`; setting either to `true` includes the filters. - `mergeHeaders` (only when `format` is `xlsx`; optional, boolean) specifies whether repeating header cells are merged (`true`) or not (`false`; default). - `pdfConfiguration` (only when `format` is `pdf`; optional, [`IExportPdfConfig`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.iexportpdfconfig/index.md)) sets `pageSize` (`A3`, `A4`, or `LETTER`), `pageOrientation` (`PORTRAIT` or `LANDSCAPE`), and `showInfoPage`. - `visualizationObjectId` (only when `format` is `pdf`; optional, string) exports the saved visualization with this identifier instead of the component's execution result. - `visualizationObjectCustomFilters` (only with `visualizationObjectId`; optional, [`IFilter[]`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-model/sdk-model.ifilter/index.md)) applies these filters to the exported visualization object without changing the one being displayed. - `grandTotalsPosition` (optional, `pinnedTop`, `pinnedBottom`, `top`, or `bottom`) sets where grand totals appear in the export and takes precedence over the position stored in the visualization object. - `timeout` (optional, number) overrides the default polling timeout for the export result, in milliseconds. Don't set it below 5000; a shorter timeout allows only one polling attempt. ### [Export result](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/on-export-ready/#export-result) The export function resolves to an `IExportResult` object: - `uri` (string) is the API URI from which you can fetch the exported file again. - `objectUrl` (string) is a browser object URL pointing at the downloaded file in memory. Use it to offer an immediate download without another network round-trip, and call `URL.revokeObjectURL()` when you no longer need it to release the memory. - `fileName` (optional, string) is the file name provided by the export service. ## [Example](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/on-export-ready/#example) ```tsx import { useRef } from "react"; import { type IExportFunction } from "@gooddata/sdk-ui"; import { InsightView } from "@gooddata/sdk-ui-ext"; export function Example() { const exportFunctionRef = useRef(null); const onExportReady = (exportFunction: IExportFunction) => { exportFunctionRef.current = exportFunction; }; const doExport = async () => { if (!exportFunctionRef.current) { return; // the visualization has not loaded yet } try { const result = await exportFunctionRef.current({ format: "xlsx", showFilters: true, mergeHeaders: true, title: "CustomTitle", }); console.log("Export succeeded: ", result.uri); } catch (error) { console.log("Export error: ", error); } }; return (
); } ``` ## [Notes](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/on-export-ready/#notes) - The callback fires each time the component finishes loading data. If the execution fails, the export function it receives rejects with the same [`GoodDataSdkError`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.gooddatasdkerror/index.md). - The [`Kpi`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.kpi/index.md) component doesn't support export. - `onExportReady` is for exporting a visualization component you render yourself, such as a standalone [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md). If you're building a dashboard plugin that replaces a widget's visualization renderer, forward `pushData` instead so the Dashboard component's own Export popup can offer CSV and XLSX for that widget — see [Enable CSV and XLSX export for custom visualization renderers](https://www.gooddata.ai/docs/gooddata-ui/latest/dashboard-plugins/dashboard-plugins-api/index.md#enable-csv-and-xlsx-export-for-custom-visualization-renderers). --- # onLegendReady > Get the legend items of a chart through the onLegendReady callback so you can render a fully custom legend. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/on-legend-ready/index.md The `onLegendReady` callback gives you the legend items of a chart so that you can render your own legend, for example outside the chart container, as a sidebar table, or shared by several charts. It's available on the chart components from `@gooddata/sdk-ui-charts` (the [`IChartCallbacks`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-charts/sdk-ui-charts.ichartcallbacks/index.md) interface); [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md), [`PivotTable`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-pivot/sdk-ui-pivot.pivottable/index.md), and [`Kpi`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.kpi/index.md) don't offer it. ## [Structure](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/on-legend-ready/#structure) ```ts onLegendReady?: (data: ILegendData) => void; ``` The callback receives an [`ILegendData`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-charts/sdk-ui-charts.ilegenddata/index.md) object: ```ts { legendItems: [ { name: "Revenue", // the name of the series color: "#14b2e2", // a CSS color string, usable directly in styles onClick: () => {}, // toggles show/hide for the series in the chart }, ], } ``` ## [Example](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/on-legend-ready/#example) ```tsx import { useState } from "react"; import { ColumnChart, type ILegendItem } from "@gooddata/sdk-ui-charts"; import * as Md from "./catalog.js"; export function ChartWithCustomLegend() { const [legendItems, setLegendItems] = useState([]); return ( <>
    {legendItems.map((item) => (
  • {item.name}
  • ))}
setLegendItems(legendData.legendItems)} /> ); } ``` ## [Notes](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/on-legend-ready/#notes) - Disable the built-in legend with `config.legend.enabled = false`; otherwise both legends render. - The callback fires once the chart has rendered its series, and again whenever the legend items change, for example after new data arrives. - Each item's `onClick` toggles the visibility of its series in the chart, so you don't need to implement show and hide yourself. The `legendItems` array doesn't change when you click; keep the hidden or shown state in your own component state to keep your legend in sync. - The `color` values follow the active color palette or the explicit `colorMapping`, so keep your legend palette-aware if you allow theme switching. --- # Handle errors > Tell an empty result from a failure, ignore canceled requests, and react to authentication and size limits by narrowing SDK errors with the is* guards instead of matching on message text. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/handle-errors/index.md Not every error deserves the same treatment. A result with no rows is an empty state, a canceled request is noise, an expired session needs a login, and a result that is too large needs a filter. GoodData.UI gives every failure a typed class and a type guard, so you can tell them apart without parsing messages. ## [Two layers of errors](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/handle-errors/#two-layers-of-errors) | Layer | Package | Base class | Where you get it | |-------|---------|------------|----------------| | Backend | `@gooddata/sdk-backend-spi` | [`AnalyticalBackendError`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.analyticalbackenderror/index.md) | Promises from `backend.workspace(...)` calls, `useCancelablePromise` | | UI | `@gooddata/sdk-ui` | [`GoodDataSdkError`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.gooddatasdkerror/index.md) | `onError` callbacks, `error` from `useExecutionDataView` and the other data hooks, [`ErrorComponent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.errorcomponent/index.md) | The UI layer wraps the backend layer. `convertError(unknown)` performs that mapping and returns a [`GoodDataSdkError`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.gooddatasdkerror/index.md) for any input (a [`GoodDataSdkError`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.gooddatasdkerror/index.md) passes through unchanged, anything unrecognized becomes an [`UnexpectedSdkError`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.unexpectedsdkerror/index.md)), so you can normalize before you branch. A [`GoodDataSdkError`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.gooddatasdkerror/index.md) exposes `getErrorCode()`, one of the values in [`ErrorCodes`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.errorcodes/index.md), `getMessage()`, and `getCause()`. ## [Handle errors in a component callback](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/handle-errors/#handle-errors-in-a-component-callback) ```tsx import { useState } from "react"; import { isCancelledSdkError, isDataTooLargeToDisplay, isNoDataSdkError, isUnauthorized, type GoodDataSdkError, } from "@gooddata/sdk-ui"; import { ColumnChart } from "@gooddata/sdk-ui-charts"; import * as Md from "./catalog.js"; type ChartState = "ok" | "empty" | "too-large" | "failed"; export function SalesByRegion() { const [state, setState] = useState("ok"); const handleError = (error: GoodDataSdkError) => { if (isCancelledSdkError(error)) { return; // a newer request replaced this one } if (isNoDataSdkError(error)) { setState("empty"); } else if (isDataTooLargeToDisplay(error)) { setState("too-large"); } else if (isUnauthorized(error)) { window.location.assign("/login"); } else { setState("failed"); } }; if (state === "empty") { return

No sales match the current filters.

; } if (state === "too-large") { return

Add a filter to narrow the result.

; } if (state === "failed") { return

The chart could not be loaded.

; } return ( null} /> ); } ``` When you show your own message, pass an [`ErrorComponent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.errorcomponent/index.md) that renders nothing; otherwise the built-in error indicator appears as well. See [ErrorComponent](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/error-component/index.md). ## [Handle errors from a direct backend call](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/handle-errors/#handle-errors-from-a-direct-backend-call) Errors from `backend.workspace(...)` are backend errors. Narrow them with the guards from `@gooddata/sdk-backend-spi`, or convert them first. ```ts import { isAbortError, isNoDataError, type IAnalyticalBackend, type IDataView } from "@gooddata/sdk-backend-spi"; import * as Md from "./catalog.js"; export async function loadTotal(backend: IAnalyticalBackend, workspace: string): Promise { try { const result = await backend.workspace(workspace).execution().forItems([Md.Amount]).execute(); return await result.readAll(); } catch (error) { if (isAbortError(error) || isNoDataError(error)) { return undefined; // canceled or empty: nothing to show } throw error; } } ``` [`NoDataError`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.nodataerror/index.md) carries the `dataView` it was raised for when one exists, so you can still read headers from an empty result. ## [Error codes and guards](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/handle-errors/#error-codes-and-guards) | [`ErrorCodes`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.errorcodes/index.md) value | UI guard | Backend guard | Typical reaction | |--------------------|----------|---------------|------------------| | `NO_DATA` | `isNoDataSdkError` | `isNoDataError` | Show an empty state | | `CANCELLED` | `isCancelledSdkError` | `isAbortError` | Ignore | | `UNAUTHORIZED` | `isUnauthorized` | `isNotAuthenticated` | Start the login flow | | `DATA_TOO_LARGE_TO_DISPLAY` | `isDataTooLargeToDisplay` | | Ask for a filter; the analytics engine computed the result but the visualization refuses to render it | | `DATA_TOO_LARGE_TO_COMPUTE` | `isDataTooLargeToCompute` | `isDataTooLargeError` | Ask for a filter; the analytics engine refused the request | | `NOT_FOUND` | `isNotFound` | `isUnexpectedResponseError` with `httpStatus` 404 | The object was deleted or the identifier is wrong | | `PROTECTED_REPORT` | `isProtectedReport` | `isProtectedDataError` | The user lacks permission for the data | | `BAD_REQUEST` | `isBadRequest` | `isUnexpectedResponseError` with `httpStatus` 400 | Fix the execution definition | | `UNKNOWN_ERROR` | `isUnknownSdkError` | `isUnexpectedError`, or `isUnexpectedResponseError` with any other `httpStatus` | Show a generic message, log the cause | `isGoodDataSdkError` and `isAnalyticalBackendError` tell the two layers apart when a `catch` block receives `unknown`. ## [Notes](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/handle-errors/#notes) - `getCause()` on a [`GoodDataSdkError`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.gooddatasdkerror/index.md) returns the wrapped backend error, including the `httpStatus` of an [`UnexpectedResponseError`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.unexpectedresponseerror/index.md). - The visualization components already map the common codes to a localized `message` and `description` (see `newErrorMapping`) before they render [`ErrorComponent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.errorcomponent/index.md). Handle errors yourself only when you want a different reaction, not different text. --- # ErrorComponent > Customize or disable the ErrorComponent indicator shown when a GoodData.UI visualization fails to render. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/error-component/index.md The [`ErrorComponent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.errorcomponent/index.md) prop lets you customize what a visualization component renders when it's in its error state. The component you pass receives [`IErrorProps`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.ierrorprops/index.md): the `message` and `description` already localized for the error code, the `code` itself, and the `icon` class name. To hide the error state, pass a component that renders nothing. All visualization components support the [`ErrorComponent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.errorcomponent/index.md) prop. The default [`ErrorComponent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.errorcomponent/index.md) is exported from `@gooddata/sdk-ui`, so you can also render it yourself; by default it uses the `gd-icon-warning` icon and fills the available height. To react differently to an empty result, a canceled request, or an expired session instead of replacing the message, handle the `onError` callback and narrow the error with the type guards; see [Handle errors](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/handle-errors/index.md). ## [Example: disabled ErrorComponent](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/error-component/#example-disabled-errorcomponent) In the following example, the [`Kpi`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.kpi/index.md) component shows neither the loading indicator nor the error message. ```tsx import { Kpi } from "@gooddata/sdk-ui"; import * as Md from "./catalog.js"; null} ErrorComponent={() => null} /> ``` ## [Example: customized ErrorComponent](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/error-component/#example-customized-errorcomponent) In the following example, the default [`ErrorComponent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.errorcomponent/index.md) is replaced by a custom component. ```tsx import { Kpi, type IErrorProps } from "@gooddata/sdk-ui"; import * as Md from "./catalog.js"; const CustomError = ({ message, description, icon }: IErrorProps) => (

{message}
{description}

); ``` ## [Props](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/error-component/#props) | Prop | Type | Required | Description | Inherited from | |------|------|----------|-------------|----------------| | [`message`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.ierrorprops/index.md#ierrorpropsmessage) | `string` | Yes | — | — | | [`className`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.ierrorprops/index.md#ierrorpropsclassname) | `string` | No | — | — | | [`clientHeight`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.ierrorprops/index.md#ierrorpropsclientheight) | `any` | No | Size of the visualisation content when custom size layouting is enabled. | — | | [`code`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.ierrorprops/index.md#ierrorpropscode) | `string` | No | — | — | | [`dataTestId`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.ierrorprops/index.md#ierrorpropsdatatestid) | `string` | No | Test identifier put on the component's root element. | — | | [`description`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.ierrorprops/index.md#ierrorpropsdescription) | `string` | No | — | — | | [`height`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.ierrorprops/index.md#ierrorpropsheight) | `any` | No | Size of the error component. | — | | [`icon`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.ierrorprops/index.md#ierrorpropsicon) | `string` | No | — | — | | [`style`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.ierrorprops/index.md#ierrorpropsstyle) | `object` | No | — | — | | [`width`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.ierrorprops/index.md#ierrorpropswidth) | `any` | No | — | — | --- # LoadingComponent > Customize or disable the LoadingComponent indicator shown while a GoodData.UI visualization fetches data. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/loading-component/index.md The [`LoadingComponent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.loadingcomponent/index.md) prop lets you customize what a visualization component renders while it's loading data. To hide the loading state, pass a component that renders nothing. All visualization components support the [`LoadingComponent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.loadingcomponent/index.md) prop. The default [`LoadingComponent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.loadingcomponent/index.md) is exported from `@gooddata/sdk-ui`, so you can also render it yourself with different props. Its defaults are a three-dot animation 8px high (`imageHeight`), animated at `speed` 1, in the theme's complementary gray (`color`), filling the available height (`height="100%"`) as a block element (`inline={false}`). ## [Example: disabled LoadingComponent](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/loading-component/#example-disabled-loadingcomponent) In the following example, the [`Kpi`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.kpi/index.md) component shows neither the loading indicator nor the error message. ```tsx import { Kpi } from "@gooddata/sdk-ui"; import * as Md from "./catalog.js"; null} ErrorComponent={() => null} />; ``` ## [Example: customized LoadingComponent](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/loading-component/#example-customized-loadingcomponent) In the following example, the default [`LoadingComponent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.loadingcomponent/index.md) is customized with a color, a fixed height, an indicator size, and an animation speed. ```tsx import { Kpi, LoadingComponent } from "@gooddata/sdk-ui"; import * as Md from "./catalog.js"; const CustomLoading = () => ; ; ``` ## [Props](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/loading-component/#props) | Prop | Type | Required | Description | Inherited from | |------|------|----------|-------------|----------------| | [`className`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.iloadingprops/index.md#iloadingpropsclassname) | `string` | No | — | — | | [`color`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.iloadingprops/index.md#iloadingpropscolor) | `string` | No | — | — | | [`height`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.iloadingprops/index.md#iloadingpropsheight) | `any` | No | — | — | | [`imageHeight`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.iloadingprops/index.md#iloadingpropsimageheight) | `any` | No | — | — | | [`imageWidth`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.iloadingprops/index.md#iloadingpropsimagewidth) | `any` | No | — | — | | [`inline`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.iloadingprops/index.md#iloadingpropsinline) | `boolean` | No | — | — | | [`speed`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.iloadingprops/index.md#iloadingpropsspeed) | `number` | No | — | — | | [`width`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.iloadingprops/index.md#iloadingpropswidth) | `any` | No | — | — | --- # Track user interactions > Track how people use the dashboards, visualizations, and AI Assistant in your GoodData.UI application, and send the events to an external service for further analytics. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/track-user-interactions/index.md Build a small event tracking layer on top of your React application. When users interact with GoodData components, the components report events, and the tracking layer forwards them to an external service of your choice. The following table lists common uses for the tracked events. | Purpose | Example services | | --- | --- | | Traffic analytics | Google Analytics 4, Adobe Analytics, Matomo, Plausible, Umami | | Product and UX analytics | Mixpanel, Amplitude, PostHog, Heap, Hotjar, Microsoft Clarity, FullStory | | Event routing | Segment, RudderStack, Jitsu, Snowplow, mParticle, Freshpaint | | Marketing messaging | Customer.io, Braze, Brevo, Iterable, Klaviyo | | Sales and customer success | HubSpot, Salesforce, Gainsight PX, ChurnZero, Totango | | Customer support | Intercom, Freshchat, Tawk.to | | Error monitoring | Sentry, Datadog RUM, New Relic Browser | You need: - A React application with [`BackendProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.backendprovider/index.md) and [`WorkspaceProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.workspaceprovider/index.md), such as the project from the [Quick start](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/index.md). - A dashboard embedded with the [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component and a visualization embedded with the [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md) component. See [Embed dashboards](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/embed-dashboards/index.md) and [Embed visualizations](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/embed-visualizations/index.md). - An external service and its browser library, such as one of those listed in the table above. If you don't have one yet, start with a console adapter that logs the events. > **Respect privacy and consent:** > Many countries protect user privacy by law, for example with the GDPR in the EU. Before you track anything, check which laws apply to your users. They may limit what personal data you can collect, or require the user's consent first. ## [Describe your events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/track-user-interactions/#describe-your-events) Start with the list of events you want to see in your reports. Define them as one TypeScript union, so the rest of the code can only send events that exist, with the right properties. Create `src/analytics/events.ts`: ```ts export type AnalyticsEvent = | { name: "dashboard_viewed"; properties: { dashboardId: string; dashboardTitle: string } } | { name: "dashboard_rendered"; properties: { dashboardId: string } } | { name: "filter_changed"; properties: FilterChangedProperties } | { name: "drill_triggered"; properties: { source: string; drillTypes: string[] } } | { name: "export_succeeded"; properties: { format: string; durationMs?: number } } | { name: "export_failed"; properties: { format: string; durationMs?: number } } | { name: "button_clicked"; properties: { button: string } } | { name: "ai_question_asked"; properties: { threadId?: string } } | { name: "ai_feedback_given"; properties: { feedback: "POSITIVE" | "NEGATIVE" | "NONE" } }; export type FilterChangedProperties = | { dashboardId: string; filterType: "attribute"; displayForm: string } | { dashboardId: string; filterType: "date"; granularity: string }; export interface IAnalyticsContext { workspaceId: string; userId?: string; path: string; } export type TrackedEvent = AnalyticsEvent & { context: IAnalyticsContext; timestamp: string; }; export type AnalyticsSink = (event: TrackedEvent) => void; ``` A few conventions keep the reports readable later: - Name events in the past tense, such as `filter_changed`, and keep the names unique and stable. Put everything that varies into properties. - Keep the shared context, such as the workspace and the user, out of the individual events. The provider in the next step adds it to every event. ## [Create the tracking provider](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/track-user-interactions/#create-the-tracking-provider) The provider is the only place that knows about the shared context. Components get a `track` function from it and call it with an `AnalyticsEvent`. The provider adds the context and a timestamp, and passes the result to the sink. The context includes the signed-in user. You can get the user with your own hook. The hook returns `undefined` until the user loads, and then a unique identifier for the user, such as the email or the user ID. Or you can use the user identity from GoodData, as in this `src/analytics/useCurrentUser.ts`: ```ts import { useBackendStrict, useCancelablePromise } from "@gooddata/sdk-ui"; export function useCurrentUser(): string | undefined { const backend = useBackendStrict(); const { result } = useCancelablePromise({ promise: () => backend.currentUser().getUser() }, [backend]); return result?.login; } ``` Now create `src/analytics/AnalyticsProvider.tsx`: ```tsx import { createContext, useCallback, useContext, type ReactNode } from "react"; import { useLocation } from "react-router"; import { useWorkspaceStrict } from "@gooddata/sdk-ui"; import { type AnalyticsEvent, type AnalyticsSink } from "./events"; import { useCurrentUser } from "./useCurrentUser"; type Track = (event: AnalyticsEvent) => void; const AnalyticsContext = createContext((event) => { console.warn(`Analytics event "${event.name}" was not tracked because no AnalyticsProvider is rendered above.`); }); interface IAnalyticsProviderProps { sink: AnalyticsSink; children: ReactNode; } export function AnalyticsProvider({ sink, children }: IAnalyticsProviderProps) { const workspaceId = useWorkspaceStrict(); const userId = useCurrentUser(); const { pathname: path } = useLocation(); // or the equivalent hook of your router const track = useCallback( (event) => { try { const context = { workspaceId, userId, path }; sink({ ...event, context, timestamp: new Date().toISOString() }); } catch (error) { console.warn("Analytics event was not sent", error); } }, [sink, workspaceId, userId, path], ); return {children}; } export const useTrack = (): Track => useContext(AnalyticsContext); ``` Two details make the provider safe to use everywhere: - The `try`/`catch` makes sure a failing analytics library never breaks a dashboard. Tracking is a side effect, so it must not throw. - The default context value only logs a warning. A component rendered outside the provider, for example in a unit test, still works. It doesn't track, and the console tells you why. Besides the workspace and the user, the example adds the current URL path to the context. Add any other values you want to track and analyze in your service, such as the application version or the current tenant. ## [Connect your external service](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/track-user-interactions/#connect-your-external-service) A sink is a function that receives a `TrackedEvent` and forwards it. Write one adapter per service you use. Each adapter maps the event to the service's own API, so switching services later means writing one new function. Create `src/analytics/sinks.ts`: ```ts import mixpanel from "mixpanel-browser"; import { type AnalyticsSink } from "./events"; // Print the events to the browser console. export const consoleSink: AnalyticsSink = (event) => { console.debug(`[analytics] ${event.name}`, event); }; // Send the events to Mixpanel. export function createMixpanelSink(): AnalyticsSink { let identifiedUser: string | undefined; return ({ name, properties, context }) => { if (context.userId && context.userId !== identifiedUser) { mixpanel.identify(context.userId); identifiedUser = context.userId; } mixpanel.track(name, { ...context, ...properties }); }; } // Send each event to several sinks. export function combineSinks(...sinks: AnalyticsSink[]): AnalyticsSink { return (event) => sinks.forEach((sink) => sink(event)); } ``` Install, initialize, and configure the service's own library the way its documentation describes. Now pick the sink and wrap your application in the provider. Render the provider inside [`BackendProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.backendprovider/index.md), [`WorkspaceProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.workspaceprovider/index.md), and your router, because it reads from all three. Create the sink once, outside the component, so its identity never changes. The console sink is useful during development, but in production it would log every event for every user, so add it only in development builds: ```tsx import { BrowserRouter } from "react-router"; import { BackendProvider, WorkspaceProvider } from "@gooddata/sdk-ui"; import { AnalyticsProvider } from "./analytics/AnalyticsProvider"; import { combineSinks, consoleSink, createMixpanelSink } from "./analytics/sinks"; import { backend } from "./backend"; const mixpanelSink = createMixpanelSink(); const sink = import.meta.env.DEV ? combineSinks(consoleSink, mixpanelSink) : mixpanelSink; export function App() { return ( ); } ``` `backend` is the backend instance your application already creates, as described in [Installation](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/installation/index.md). ## [Track dashboard events](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/track-user-interactions/#track-dashboard-events) The [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component emits an event for everything that happens inside it. Turn the events you care about into `AnalyticsEvent`s with a set of dashboard event handlers. To learn more about the events and their handlers, see [Handle dashboard events](https://www.gooddata.ai/docs/gooddata-ui/latest/dashboard-plugins/dashboard-events/index.md). Create `src/analytics/useDashboardAnalytics.ts`: ```ts import { useMemo } from "react"; import { objRefToString } from "@gooddata/sdk-model"; import { isDashboardAttributeFilterSelectionChanged, isDashboardDateFilterSelectionChanged, isDashboardInitialized, isDashboardRenderResolved, type DashboardAttributeFilterSelectionChanged, type DashboardDateFilterSelectionChanged, type DashboardEventHandler, type DashboardInitialized, type DashboardRenderResolved, } from "@gooddata/sdk-ui-dashboard"; import { useTrack } from "./AnalyticsProvider"; export function useDashboardAnalytics(dashboardId: string): DashboardEventHandler[] { const track = useTrack(); return useMemo(() => { const viewed: DashboardEventHandler = { eval: isDashboardInitialized, handler: (event) => { const dashboardTitle = event.payload.dashboard?.title ?? ""; track({ name: "dashboard_viewed", properties: { dashboardId, dashboardTitle } }); }, }; const rendered: DashboardEventHandler = { eval: isDashboardRenderResolved, handler: () => { track({ name: "dashboard_rendered", properties: { dashboardId } }); }, }; const attributeFilterChanged: DashboardEventHandler = { eval: isDashboardAttributeFilterSelectionChanged, handler: (event) => { const displayForm = objRefToString(event.payload.filter.attributeFilter.displayForm); track({ name: "filter_changed", properties: { dashboardId, filterType: "attribute", displayForm } }); }, }; const dateFilterChanged: DashboardEventHandler = { eval: isDashboardDateFilterSelectionChanged, handler: (event) => { const granularity = event.payload.filter?.dateFilter.granularity ?? "allTime"; track({ name: "filter_changed", properties: { dashboardId, filterType: "date", granularity } }); }, }; return [viewed, rendered, attributeFilterChanged, dateFilterChanged]; }, [track, dashboardId]); } ``` > **Check the API maturity of the events you track:** > Some events are marked as `@alpha` or `@beta`. Their payloads can change in a minor release. See [API maturity](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/api-maturity/index.md). The [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component compares its `eventHandlers` prop by reference. When it gets a new array, it throws away its state and loads the dashboard again, which cancels anything in progress, such as a running export. So make sure you memoize the handlers together with everything they depend on, including the `track` function. Now render the dashboard with the memoized handlers: ```tsx import { Dashboard } from "@gooddata/sdk-ui-dashboard"; import { useDashboardAnalytics } from "./analytics/useDashboardAnalytics"; export function DashboardPage({ dashboardId }: { dashboardId: string }) { const analyticsHandlers = useDashboardAnalytics(dashboardId); return ; } ``` If your application already passes its own handlers to the [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component, merge them into the same array. Open the page and look at the browser console. You should see `dashboard_viewed` as soon as the dashboard loads, and `dashboard_rendered` a moment later. Change a filter, and `filter_changed` follows. ## [Track drills and exports on the dashboard](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/track-user-interactions/#track-drills-and-exports-on-the-dashboard) The [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component reports drills and exports with events, too. Add the following imports to the existing import block from `@gooddata/sdk-ui-dashboard` in `useDashboardAnalytics.ts`, without repeating the ones that are already there: ```ts import { isDashboardDrillResolved, isDashboardExportToPdfResolved, type IDashboardDrillResolved, type IDashboardExportToPdfResolved, } from "@gooddata/sdk-ui-dashboard"; ``` Note that some event types start with `I`, such as [`IDashboardDrillResolved`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.idashboarddrillresolved/index.md), and others don't, such as [`DashboardInitialized`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboardinitialized/index.md). Then add the handlers inside the `useMemo` callback, next to the existing ones: ```ts const drilled: DashboardEventHandler = { eval: isDashboardDrillResolved, handler: (event) => { const drillTypes = event.payload.drillEvent.drillDefinitions.map((definition) => definition.type); track({ name: "drill_triggered", properties: { source: "dashboard", drillTypes } }); }, }; const exportedToPdf: DashboardEventHandler = { eval: isDashboardExportToPdfResolved, handler: () => { track({ name: "export_succeeded", properties: { format: "pdf" } }); }, }; ``` And update the return line: ```ts return [viewed, rendered, attributeFilterChanged, dateFilterChanged, drilled, exportedToPdf]; ``` `drillTypes` tells you which kinds of drill people use, such as `drillToInsight`, `drillToDashboard`, or `drillDown`. ## [Track standalone visualizations](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/track-user-interactions/#track-standalone-visualizations) Visualizations you render yourself, such as [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md) or [`ColumnChart`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-charts/sdk-ui-charts.columnchart/index.md), don't emit dashboard events. They report through callback props instead. Use `onDrill` for drills, `onLoadingChanged` for load time, and `onExportReady` for exports. ```tsx import { useRef } from "react"; import { type IExportFunction, type IExtendedExportConfig } from "@gooddata/sdk-ui"; import { InsightView } from "@gooddata/sdk-ui-ext"; import { useTrack } from "./analytics/AnalyticsProvider"; export function RevenueTrend() { const track = useTrack(); const exportRef = useRef(null); const onExportReady = (exportFunction: IExportFunction) => { exportRef.current = async (config: IExtendedExportConfig) => { const startedAt = performance.now(); const format = config.format ?? "csv"; try { const result = await exportFunction(config); const durationMs = Math.round(performance.now() - startedAt); track({ name: "export_succeeded", properties: { format, durationMs } }); return result; } catch (error) { const durationMs = Math.round(performance.now() - startedAt); track({ name: "export_failed", properties: { format, durationMs } }); throw error; } }; }; return ( <> { const drillTypes = [event.drillContext.type]; track({ name: "drill_triggered", properties: { source: "revenue-trend", drillTypes } }); }} /> ); } ``` The example wraps the export function from `onExportReady` before it stores it, so every export of this visualization is tracked, wherever your code calls it from. The wrapper measures the duration and reports failures too, and it rethrows the error so your own error handling still works. In the same way, you can track load time with `onLoadingChanged` and failures with `onError`. See [Handle errors](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/handle-errors/index.md). ## [Track the AI Assistant](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/track-user-interactions/#track-the-ai-assistant) The [`GenAIAssistant`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-gen-ai/sdk-ui-gen-ai.genaiassistant/index.md) component takes an `eventHandlers` prop. Its handlers have the same `eval` and `handler` shape as the ones you pass to the [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component. Track the questions people ask and the feedback they give: ```tsx import { GenAIAssistant, isChatFeedbackEvent, isChatUserMessageEvent, type ChatEventHandler, type ChatFeedbackEvent, type ChatUserMessageEvent, } from "@gooddata/sdk-ui-gen-ai"; import { useMemo } from "react"; import { useTrack } from "./analytics/AnalyticsProvider"; export function Assistant() { const track = useTrack(); const eventHandlers = useMemo(() => { const questionAsked: ChatEventHandler = { eval: isChatUserMessageEvent, handler: (event) => track({ name: "ai_question_asked", properties: { threadId: event.threadId } }), }; const feedbackGiven: ChatEventHandler = { eval: isChatFeedbackEvent, handler: (event) => track({ name: "ai_feedback_given", properties: { feedback: event.feedback } }), }; return [questionAsked, feedbackGiven]; }, [track]); return ; } ``` [`ChatUserMessageEvent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-gen-ai/sdk-ui-gen-ai.chatusermessageevent/index.md) also carries the text of the question in `event.question`. Send it only if your privacy policy allows it, because people often type customer names and other sensitive data into a chat. ## [Track your own controls](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/interactivity/track-user-interactions/#track-your-own-controls) Not everything worth tracking happens inside a GoodData.UI component. Your navigation, tabs, and buttons are plain React, so call `track` from their event handlers, such as `onClick`: ```tsx import { useNavigate } from "react-router"; import { useTrack } from "./analytics/AnalyticsProvider"; export function SalesReportButton() { const track = useTrack(); const navigate = useNavigate(); return ( ); } ``` Because these events go through the same provider, they get the same workspace, user, and path as the events from the dashboard. You can put them side by side in one report. --- You now have one tracking layer for your whole application. The [`Dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboard/index.md) component, standalone visualizations, the AI Assistant, and your own controls all report typed events to it. The layer adds the shared context and forwards the events to the services you choose. Now you can analyze what your users do and experience. To track something new, add an event to the union and call `track`. To switch services, write one new sink. --- # Hooks > Choose the matching React hook to read an SDK provider context or load a plugin-enhanced dashboard. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/index.md React hooks for the SDK context providers: backend, workspace, organization, and theme. Each reads from the matching `*Provider` higher in the component tree, so you don't pass these values down through props. `useBackend`, `useWorkspace`, and `useTheme` take an optional value that overrides the context. Their strict variants, `useBackendStrict` and `useWorkspaceStrict`, throw when neither the argument nor the provider supplies a value. To load and render a dashboard with its plugins, use `useDashboardLoader`. --- # useBackend > Retrieve the analytical backend from BackendProvider context, with an optional prop override or strict variant. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-backend/index.md `useBackend` returns the analytical backend provided to [`BackendProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.backendprovider/index.md). If you pass a backend, the hook returns it instead, so a component can accept a `backend` prop that wins over the context. `useBackendStrict` does the same but throws when no backend is available. ## [Basic usage](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-backend/#basic-usage) ```tsx import { useBackend } from "@gooddata/sdk-ui"; function MyComponent({ backend: backendProp }) { // Uses backendProp if defined, otherwise falls back to context value const backend = useBackend(backendProp); // backend may be undefined if neither prop nor context is set if (!backend) { return
No backend configured
; } // Use backend to interact with GoodData // ... } ``` ### [Require a backend](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-backend/#require-a-backend) `useBackendStrict` throws an invariant error when neither the parameter nor the context supplies a backend. Use it when a backend is required and you want a clear error instead of handling `undefined`. ```tsx import { useBackendStrict } from "@gooddata/sdk-ui"; function MyComponent({ backend: backendProp }) { // Throws if no backend is available const backend = useBackendStrict(backendProp); // backend is always defined here const workspaces = backend.workspaces(); // ... } ``` ## [Parameters](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-backend/#parameters) | Parameter | Type | Description | |-----------|------|-------------| | `backend` | `IAnalyticalBackend \| undefined` | Optional backend override. If provided, returned directly instead of context value. | ## [Return value](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-backend/#return-value) - `useBackend`: `IAnalyticalBackend | undefined` - `useBackendStrict`: [`IAnalyticalBackend`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md) (throws if unavailable) ## [Notes](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-backend/#notes) - The `backend` parameter takes precedence over the context value. The hook returns `backend ?? backendFromContext`. - Without a [`BackendProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.backendprovider/index.md) up the tree and without the `backend` parameter, `useBackend` returns `undefined`. It doesn't throw. - `useBackendStrict` accepts a second, optional `context: string` parameter (default `"useBackendStrict"`). It prefixes the invariant error message, so you can pass the name of the calling hook or component. - When no backend is available, `useBackendStrict` raises an invariant error with this message: `: IAnalyticalBackend must be defined. Either pass it as a parameter or make sure there is a BackendProvider up the component tree.` ## [Props](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-backend/#props) | Prop | Type | Required | Description | Inherited from | |------|------|----------|-------------|----------------| | [`authenticate`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md#ianalyticalbackendauthenticate) | `(force?: boolean) => Promise<`[`IAuthenticatedPrincipal`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.iauthenticatedprincipal/index.md)`>` | Yes | Triggers authentication process against the backend. | — | | [`capabilities`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md#ianalyticalbackendcapabilities) | [`IBackendCapabilities`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ibackendcapabilities/index.md) | Yes | Capabilities available on this backend. | — | | [`config`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md#ianalyticalbackendconfig) | [`IAnalyticalBackendConfig`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackendconfig/index.md) | Yes | Configuration used for communication with this backend. | — | | [`currentUser`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md#ianalyticalbackendcurrentuser) | `() =>` [`IUserService`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.iuserservice/index.md) | Yes | Returns a service for interacting with the currently authenticated user. | — | | [`dataSources`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md#ianalyticalbackenddatasources) | `() =>` [`IDataSourcesService`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.idatasourcesservice/index.md) | Yes | Returns service that can be used to work with data sources. | — | | [`deauthenticate`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md#ianalyticalbackenddeauthenticate) | `(returnTo?: string) => Promise` | Yes | Triggers deauthentication process against the backend. | — | | [`entitlements`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md#ianalyticalbackendentitlements) | `() =>` [`IEntitlements`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ientitlements/index.md) | Yes | Returns service that can be used to obtain license entitlements. | — | | [`geo`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md#ianalyticalbackendgeo) | `() =>` [`IGeoService`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.igeoservice/index.md) | Yes | Returns service that can be used to load geo location assets. | — | | [`isAuthenticated`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md#ianalyticalbackendisauthenticated) | `() => Promise<`[`IAuthenticatedPrincipal`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.iauthenticatedprincipal/index.md) `\| null>` | Yes | Tests authentication against this backend. | — | | [`onHostname`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md#ianalyticalbackendonhostname) | `(hostname: string) =>` [`IAnalyticalBackend`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md) | Yes | Creates new instance of backend on the provided hostname. | — | | [`organization`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md#ianalyticalbackendorganization) | `(organizationId: string) =>` [`IOrganization`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.iorganization/index.md) | Yes | Returns an organization available on the backend. | — | | [`organizations`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md#ianalyticalbackendorganizations) | `() =>` [`IOrganizations`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.iorganizations/index.md) | Yes | Returns a service that can be obtained to obtain organizations. | — | | [`withAuthentication`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md#ianalyticalbackendwithauthentication) | `(provider:` [`IAuthenticationProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.iauthenticationprovider/index.md)`) =>` [`IAnalyticalBackend`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md) | Yes | Sets authentication provider to be used when backend discovers current session is not authenticated. | — | | [`withCorrelation`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md#ianalyticalbackendwithcorrelation) | `(correlationMetadata:` [`IRequestCorrelationMetadata`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.irequestcorrelationmetadata/index.md)`) =>` [`IAnalyticalBackend`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md) | Yes | Sets request correlation metadata that will be included in HTTP requests to the backend. If the IAnalyticalBackend instance already has correlation metadata set, the new metadata will be merged with the existing one. This method provides more flexibility than withTelemetry by allowing arbitrary key-value pairs to be sent. | — | | [`withTelemetry`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md#ianalyticalbackendwithtelemetry) | `(componentName: string, props: object) =>` [`IAnalyticalBackend`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md) | Yes | Sets telemetry information that SHOULD be sent to backend to track component usage. | — | | [`workspace`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md#ianalyticalbackendworkspace) | `(id: string) =>` [`IAnalyticalWorkspace`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalworkspace/index.md) | Yes | Returns an analytical workspace available on this backend. | — | | [`workspaces`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md#ianalyticalbackendworkspaces) | `() =>` [`IWorkspacesQueryFactory`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.iworkspacesqueryfactory/index.md) | Yes | Returns service that can be used to obtain available workspaces. | — | --- # useWorkspace > Read the workspace identifier from WorkspaceProvider context, with an optional override and a strict variant that throws when unset. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-workspace/index.md `useWorkspace` returns the workspace identifier provided to [`WorkspaceProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.workspaceprovider/index.md). If you pass a workspace identifier, the hook returns it instead, so a component can accept a `workspace` prop that wins over the context. `useWorkspaceStrict` does the same but throws when no workspace is available. ## [Basic usage](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-workspace/#basic-usage) ```tsx import { useWorkspace } from "@gooddata/sdk-ui"; function MyComponent({ workspace: workspaceProp }: { workspace?: string }) { // Uses workspaceProp if defined, otherwise falls back to the context value const workspace = useWorkspace(workspaceProp); if (!workspace) { return
No workspace configured
; } // Use the workspace identifier for data fetching // ... } ``` ### [Require a workspace](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-workspace/#require-a-workspace) Use `useWorkspaceStrict` in components that can't work without a workspace. It throws an invariant error when neither the parameter nor the context supplies one, so a misconfigured provider tree fails early: ```tsx import { useWorkspaceStrict } from "@gooddata/sdk-ui"; function MyComponent({ workspace: workspaceProp }: { workspace?: string }) { // Throws if no workspace is available const workspace = useWorkspaceStrict(workspaceProp); // workspace is always a string here // ... } ``` ## [Parameters](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-workspace/#parameters) | Parameter | Type | Description | |-----------|------|-------------| | `workspace` | `string \| undefined` | Optional workspace override. If defined, the hook returns it instead of the context value. | | `context` | `string` | `useWorkspaceStrict` only. Optional prefix of the error message, `"useWorkspaceStrict"` by default. Pass the name of the calling hook or component. | ## [Return value](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-workspace/#return-value) - `useWorkspace`: `string | undefined` - `useWorkspaceStrict`: `string` (throws if unavailable) ## [Notes](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-workspace/#notes) - The override wins only when it's defined. Passing `undefined` is the same as passing nothing. - Components re-render when [`WorkspaceProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.workspaceprovider/index.md) receives a new `workspace` value, so hooks that key cached data on the workspace pick up the change. - When no workspace is available, `useWorkspaceStrict` throws with this message: `: workspace must be defined. Either pass it as a parameter or make sure there is a WorkspaceProvider up the component tree.` --- # useOrganization > Read the organization identifier supplied to OrganizationProvider, returning the id string or undefined if unset. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-organization/index.md `useOrganization` returns the organization identifier provided to [`OrganizationProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.organizationprovider/index.md). Use it to reach organization-level services, such as organization settings or users, through `backend.organization(id)`. ## [Basic usage](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-organization/#basic-usage) ```tsx import { useBackendStrict, useOrganization } from "@gooddata/sdk-ui"; function MyComponent() { const backend = useBackendStrict(); const organization = useOrganization(); if (!organization) { return
No organization configured
; } // Access organization-level services const orgService = backend.organization(organization); // ... } ``` ## [Parameters](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-organization/#parameters) None. ## [Return value](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-organization/#return-value) `string | undefined`: the organization identifier, or `undefined` when no [`OrganizationProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.organizationprovider/index.md) up the tree sets one. ## [Notes](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-organization/#notes) - Components re-render when [`OrganizationProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.organizationprovider/index.md) receives a new `organization` value. - There is no strict variant. If your code can't work without an organization, check for `undefined` and render a fallback or throw, as in the example. - The hook only returns the identifier; it makes no backend call. Combine it with a backend, for example from [`useBackendStrict`](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-backend/index.md), to load organization resources. --- # useTheme > Read the current theme from ThemeProvider context, with companion hooks for loading state and dark-mode detection. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-theme/index.md `useTheme` returns the current theme from [`ThemeProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.themeprovider/index.md). If you pass a theme, the hook returns that theme instead. The companion hooks `useThemeIsLoading`, `useThemeStatus`, and `useIsDarkTheme` report the loading state and whether the theme is dark. See [Apply theming](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/embedding/apply-theming/index.md) for setting up [`ThemeProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.themeprovider/index.md). ## [Basic usage](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-theme/#basic-usage) ```tsx import type { ITheme } from "@gooddata/sdk-model"; import { useTheme } from "@gooddata/sdk-ui-theme-provider"; function MyComponent({ theme: themeProp }: { theme?: ITheme }) { // Uses themeProp if defined, otherwise falls back to the context value const theme = useTheme(themeProp); const primaryColor = theme?.palette?.primary?.base ?? "#14b2a9"; return
Themed content
; } ``` ### [Wait for the theme to load](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-theme/#wait-for-the-theme-to-load) `useThemeIsLoading` tells you whether [`ThemeProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.themeprovider/index.md) is still loading the theme from the backend: ```tsx import { useTheme, useThemeIsLoading } from "@gooddata/sdk-ui-theme-provider"; function MyComponent() { const theme = useTheme(); const isLoading = useThemeIsLoading(); if (isLoading) { return
Loading theme...
; } return
Ready
; } ``` ### [Detect a dark theme](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-theme/#detect-a-dark-theme) `useIsDarkTheme` tells you whether the current theme is dark: ```tsx import { useIsDarkTheme } from "@gooddata/sdk-ui-theme-provider"; function MyComponent() { const isDark = useIsDarkTheme(); return
Content
; } ``` ## [Parameters](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-theme/#parameters) | Parameter | Type | Description | |-----------|------|-------------| | `theme` | `ITheme \| undefined` | Optional theme override for `useTheme`. If defined, the hook returns it instead of the context value. | `useThemeIsLoading`, `useThemeStatus`, and `useIsDarkTheme` take no parameters. ## [Return value](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-theme/#return-value) - `useTheme`: `ITheme | undefined` - `useThemeIsLoading`: `boolean | undefined` - `useThemeStatus`: `"pending" | "loading" | "success" | undefined` - `useIsDarkTheme`: `boolean` ## [Notes](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-theme/#notes) - Without a [`ThemeProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.themeprovider/index.md) up the tree, `useTheme` returns `undefined`, so guard palette lookups with optional chaining or a fallback value. - `useThemeStatus` tells apart `"pending"` (no load started), `"loading"` (the theme is being fetched), and `"success"` (the theme is resolved). Use it when `useThemeIsLoading` isn't precise enough. - `useIsDarkTheme` returns `false` when there is no theme, so it's safe to call outside [`ThemeProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.themeprovider/index.md). --- # useDashboardLoader > Load, bootstrap, and tear down a plugin-enhanced dashboard from React, returning status, result, and error state. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-dashboard-loader/index.md `useDashboardLoader` loads a dashboard together with its plugins, runs the plugins' registration, and returns a `DashboardComponent` ready to render. It's the main hook for embedding a plugin-enhanced dashboard in React. For the webpack setup that plugins linked to a dashboard need, and for combining them with your own plugins, see [Embed a dashboard with plugins](https://www.gooddata.ai/docs/gooddata-ui/latest/dashboard-plugins/embed-dashboard-with-plugins/index.md). ## [Basic usage](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-dashboard-loader/#basic-usage) ```tsx import { useDashboardLoader } from "@gooddata/sdk-ui-loaders"; function MyDashboard({ dashboardRef }) { const { status, result, error } = useDashboardLoader({ dashboard: dashboardRef, // Use "adaptive" with adaptiveLoadOptions to load plugins linked to the dashboard on the backend. loadingMode: "staticOnly", // See IDashboardLoadOptions for full configuration }); if (status === "loading" || status === "pending") { return
Loading dashboard...
; } if (status === "error") { return
Error: {error.message}
; } // result contains the loaded dashboard with plugins applied const { DashboardComponent, props } = result; return ; } ``` ### [Render with DashboardStub](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-dashboard-loader/#render-with-dashboardstub) [`DashboardStub`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.dashboardstub/index.md) wraps `useDashboardLoader` and renders the loading, error, and success states for you. Use it instead of the hook when you don't need custom branching on `status` — pass it the same [`IDashboardLoadOptions`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.idashboardloadoptions/index.md), plus an optional [`ErrorComponent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.errorcomponent/index.md) and [`LoadingComponent`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.loadingcomponent/index.md): ```tsx import { DashboardStub } from "@gooddata/sdk-ui-loaders"; function MyDashboard({ dashboardRef }) { return (
Error: {message}
} LoadingComponent={() =>
Loading dashboard...
} /> ); } ``` Reach for the hook directly instead when you need to render something other than the dashboard while it loads (for example, a skeleton laid out around other page content) or when you need the `result`/`error` values for something besides rendering. ### [Add layout content from a plugin passed through `extraPlugins`](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-dashboard-loader/#add-layout-content-from-a-plugin-passed-through-extraplugins) A plugin registered through `extraPlugins` can insert widgets into the loaded dashboard from its `register` method, using the layout customization API (`customize.layout().customizeFluidLayout()`): ```tsx import { DashboardPluginV1, newCustomWidget, newDashboardItem, type DashboardContext, type IDashboardCustomizer, } from "@gooddata/sdk-ui-dashboard"; import { useDashboardLoader } from "@gooddata/sdk-ui-loaders"; class AddSectionPlugin extends DashboardPluginV1 { public readonly author = "acme"; public readonly displayName = "Add section plugin"; public readonly version = "1.0"; public readonly minEngineVersion = "bundled"; public register(_ctx: DashboardContext, customize: IDashboardCustomizer): void { customize.layout().customizeFluidLayout((_layout, layoutCustomizer) => { layoutCustomizer.addSectionToPath( { sectionIndex: 0 }, { type: "IDashboardLayoutSection", items: [newDashboardItem(newCustomWidget("myWidget", "myCustomWidget"), 12)], }, ); }); } } const { status, result } = useDashboardLoader({ dashboard: dashboardRef, loadingMode: "staticOnly", extraPlugins: { factory: () => new AddSectionPlugin() }, }); ``` ### [Set initial filters two ways](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-dashboard-loader/#set-initial-filters-two-ways) To change the filters a dashboard opens with, either pass an already loaded [`IDashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-model/sdk-model.idashboard/index.md) with a modified `filterContext` as the `dashboard` option, or pass `overrideDefaultFilters` through `config` at load time: ```tsx // `dashboard` is an IDashboard you loaded, for example with // backend.workspace(workspace).dashboards().getDashboard(dashboardRef) useDashboardLoader({ dashboard: { ...dashboard, filterContext: withMyFilter(dashboard.filterContext) }, loadingMode: "staticOnly", }); ``` ```tsx useDashboardLoader({ dashboard: dashboardRef, loadingMode: "staticOnly", config: { overrideDefaultFilters: myFilters }, }); ``` Passing a modified [`IDashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-model/sdk-model.idashboard/index.md) changes the stored filter context the dashboard starts from; passing `config.overrideDefaultFilters` keeps the stored filter context and applies your filters on top of it on every load, including reloads triggered by a change in the hook's dependencies. ### [Override chrome components on the loaded dashboard](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-dashboard-loader/#override-chrome-components-on-the-loaded-dashboard) The `DashboardComponent` the loader returns accepts the same customization props as ``. Spread `result.props` and override the ones you need, such as `FilterBarComponent` or `TopBarComponent`: ```tsx const { DashboardComponent, props } = result; return ; ``` See [Dashboard component](https://www.gooddata.ai/docs/gooddata-ui/latest/dashboard-plugins/dashboard/index.md) for the full list of overridable components. ### [Dispatch dashboard commands from a plugin-provided component](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-dashboard-loader/#dispatch-dashboard-commands-from-a-plugin-provided-component) A component a plugin registers (for example, a custom filter provider) runs inside the loaded dashboard's store context, so it can dispatch commands the same way any other dashboard-rendered component does. See [useDispatchDashboardCommand](https://www.gooddata.ai/docs/gooddata-ui/latest/dashboard-plugins/dashboard/index.md#commands). ### [Load a dashboard outside React](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-dashboard-loader/#load-a-dashboard-outside-react) The [`DashboardLoader`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.dashboardloader/index.md) builder class does the same loading without React. Use it when you need to load a dashboard outside a component, or need more control over the loading sequence than the hook's options give you: ```tsx import { DashboardLoader } from "@gooddata/sdk-ui-loaders"; const { DashboardComponent, props } = await DashboardLoader.staticOnly() .withBaseProps({ workspace, dashboard: dashboardRef }) .withEmbeddedPlugins({ factory: () => new MyPlugin() }) .load(); ``` Start the chain with `DashboardLoader.staticOnly()` or `DashboardLoader.adaptive(adaptiveLoadOptions)`, configure it with `withBaseProps`, `withEmbeddedPlugins`, `withFilterContext`, `fromClientWorkspace`, `fromWorkspace`, `forDashboard`, or `onBackend`, then call `load()` to resolve the same [`DashboardLoadResult`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.dashboardloadresult/index.md) the hook's `result` carries. ## [Parameters](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-dashboard-loader/#parameters) | Parameter | Type | Description | |-----------|------|-------------| | `options` | [`IDashboardLoadOptions`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.idashboardloadoptions/index.md) | Load options including the dashboard reference, the loading mode, and the plugin configuration. | The options that decide how plugins load: - `loadingMode`: `"adaptive"` (default) loads and bootstraps the plugins linked to the dashboard on the backend and requires `adaptiveLoadOptions`, the Module Federation integration of your bundler; the hook throws an invariant error without them. `"staticOnly"` skips dynamic plugin loading and uses only the plugins in `extraPlugins`, for environments where running remote plugin code isn't acceptable or that don't use Module Federation. - `extraPlugins`: plugins bundled with your application. - `allowUnfinishedFeatures`: `"alwaysPrevent"` (default), `"staticOnly"`, or `"alwaysAllow"`; decides whether the dashboard exposes features still in preview. ## [Return value](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-dashboard-loader/#return-value) [`DashboardLoadStatus`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.dashboardloadstatus/index.md), a `UseCancelablePromiseState` with: - `status`: `"pending" | "loading" | "success" | "error"` - `result`: [`DashboardLoadResult`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.dashboardloadresult/index.md) (on success), which holds the loaded `DashboardComponent` and its `props` - `error`: Error details (on error) ## [Notes](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-dashboard-loader/#notes) - When `dashboard`, `backend`, `workspace`, `filterContextRef`, `config`, `permissions`, `clientWorkspace`, `extraPlugins`, or `loadingMode` change, the hook loads the dashboard again and cancels the load in flight. - When the component that uses the hook unmounts, the hook calls `onPluginUnload` on each loaded plugin. - The hook logs the engine version and the plugin list with `console.debug`, which helps when you diagnose plugin registration. - Pass `backend` and `workspace` as options, or render the hook inside [`BackendProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.backendprovider/index.md) and [`WorkspaceProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.workspaceprovider/index.md). ## [Props](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/hooks/use-dashboard-loader/#props) | Prop | Type | Required | Description | Inherited from | |------|------|----------|-------------|----------------| | [`adaptiveLoadOptions`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.idashboardloadoptions/index.md#idashboardloadoptionsadaptiveloadoptions) | [`AdaptiveLoadOptions`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.adaptiveloadoptions/index.md) | No | Specify configuration related adaptive loading. | — | | [`adhocItems`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.idashboardloadoptions/index.md#idashboardloadoptionsadhocitems) | [`DashboardItem`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboarditem/index.md)`[]` | No | List of ad-hoc items to render in the dashboard. These items are not part of the dashboard definition and are not persisted, it will be rendered into empty layout. | — | | [`allowUnfinishedFeatures`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.idashboardloadoptions/index.md#idashboardloadoptionsallowunfinishedfeatures) | `"staticOnly" \| "alwaysAllow" \| "alwaysPrevent"` | No | Specify when will be in progress features allowed | — | | [`clientWorkspace`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.idashboardloadoptions/index.md#idashboardloadoptionsclientworkspace) | [`IClientWorkspaceIdentifiers`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui/sdk-ui.iclientworkspaceidentifiers/index.md) | No | Specify client workspace identifiers to use in order to identify exact workspace to load the dashboard from. | — | | [`extraPlugins`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.idashboardloadoptions/index.md#idashboardloadoptionsextraplugins) | [`IEmbeddedPlugin`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.iembeddedplugin/index.md) `\|` [`IEmbeddedPlugin`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.iembeddedplugin/index.md)`[]` | No | Specify extra plugins to use during the bootstrap of the dashboard. | — | | [`loadingMode`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.idashboardloadoptions/index.md#idashboardloadoptionsloadingmode) | [`DashboardLoadingMode`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.dashboardloadingmode/index.md) | No | Loading mode to use. | — | | [`dashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.idashboardbasepropsforloader/index.md#idashboardbasepropsforloaderdashboard) | `string \|` [`ObjRef`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-model/sdk-model.objref/index.md) `\| undefined \|` [`IDashboard`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-model/sdk-model.idashboard/index.md) | Yes | Specify reference to an existing dashboard that should be loaded or undefined to initialize an empty one. | [`IDashboardBasePropsForLoader`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-loaders/sdk-ui-loaders.idashboardbasepropsforloader/index.md) | | [`backend`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.idashboardbaseprops/index.md#idashboardbasepropsbackend) | [`IAnalyticalBackend`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-backend-spi/sdk-backend-spi.ianalyticalbackend/index.md) | No | Analytical backend from which the dashboard obtains data to render. | [`IDashboardBaseProps`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.idashboardbaseprops/index.md) | | [`config`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.idashboardbaseprops/index.md#idashboardbasepropsconfig) | [`DashboardConfig`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.dashboardconfig/index.md) | No | Configuration that can be used to modify dashboard features, capabilities and behavior. | [`IDashboardBaseProps`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.idashboardbaseprops/index.md) | | [`filterContextRef`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.idashboardbaseprops/index.md#idashboardbasepropsfiltercontextref) | [`ObjRef`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-model/sdk-model.objref/index.md) | No | Specify reference to a filter context that should be used instead of the default, built-in filter context. | [`IDashboardBaseProps`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.idashboardbaseprops/index.md) | | [`initialTabId`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.idashboardbaseprops/index.md#idashboardbasepropsinitialtabid) | `string` | No | _(Alpha)_ Explicitly specify which tab should be opened first when the dashboard is rendered. | [`IDashboardBaseProps`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.idashboardbaseprops/index.md) | | [`keyboardNavigation`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.idashboardbaseprops/index.md#idashboardbasepropskeyboardnavigation) | `KeyboardNavigationConfig` | No | Specify tab index and addressable id for the dashboard components used by keyboard navigation | [`IDashboardBaseProps`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.idashboardbaseprops/index.md) | | [`permissions`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.idashboardbaseprops/index.md#idashboardbasepropspermissions) | [`IWorkspacePermissions`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-model/sdk-model.iworkspacepermissions/index.md) | No | Specify permissions to use when determining availability of the different features of the dashboard component. | [`IDashboardBaseProps`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.idashboardbaseprops/index.md) | | [`workspace`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.idashboardbaseprops/index.md#idashboardbasepropsworkspace) | `string` | No | Identifier of analytical workspace, from which the dashboard obtains data to render. | [`IDashboardBaseProps`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-dashboard/sdk-ui-dashboard.idashboardbaseprops/index.md) | --- # Platform > Evaluate whether a GoodData.UI version and integration approach are suitable for your application and production environment. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/index.md Use these pages to see how analytics as code fits with GoodData.UI, understand the package layering and the ESM distribution, check version lifecycle and framework support, review known limitations, and read the API maturity annotations. For the Node.js, React, and TypeScript versions GoodData.UI needs, see [Platform compatibility](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/platform-compatibility/index.md). --- # Analytics as code > What GoodData means by analytics as code, which tools manage the analytics in a GoodData workspace as files, and how that work connects to a GoodData.UI application. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/analytics-as-code/index.md GoodData.UI is the code that shows analytics in your application. The analytics themselves (the data model, metrics, visualizations, and dashboards) live in a GoodData workspace, and GoodData lets you manage them as code too. This page explains what that means, which tools do it, and where the two meet. ## [What analytics as code means](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/analytics-as-code/#what-analytics-as-code-means) Instead of building every metric and dashboard by hand in the GoodData web interface, you describe them in text files and keep the files in a Git repository, next to your application code. Each object is a YAML file: ```yaml type: metric id: percent_revenue title: "% Revenue" description: Percent of Total Revenue maql: SELECT {metric/revenue} / {metric/total_revenue} format: "#,##0.0%" ``` With analytics in files, you can treat them the way you treat code: - **Version control.** Every change is in the history: what changed, when, who changed it, and why. You can revert one. - **Review.** Changes go through pull requests and code review, on GitHub, GitLab, or wherever your code lives. - **Automation.** A CI/CD pipeline validates the files and deploys them to a workspace, for example to a test workspace on every pull request and to production on release. The files cover datasets and date datasets (the logical data model), metrics written in MAQL, visualizations, dashboards, and dashboard plugins. See [Object Structures](https://www.gooddata.ai/docs/cloud/api-and-sdk/vs-code-extension/structures/) for the format of each. ## [The tools](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/analytics-as-code/#the-tools) | Tool | What it does | Docs | | --- | --- | --- | | GoodData for VS Code | An extension for Visual Studio Code: autocomplete, syntax highlighting, and validation for the YAML files, and a preview of most objects. | [GoodData for VS Code](https://www.gooddata.ai/docs/cloud/api-and-sdk/vs-code-extension/) | | GoodData CLI (`gd`) | Sets up a project (`gd init`), downloads a workspace into files (`gd clone`), checks the files (`gd validate`), and deploys them to a workspace (`gd deploy`). Built for CI/CD pipelines. Install it with `npm i -g @gooddata/code-cli`. | [GoodData Command-line Interface](https://www.gooddata.ai/docs/cloud/api-and-sdk/vs-code-extension/cli/) | | Python SDK | Reads datasets and metrics, computes visualizations, and helps you set up CI/CD pipelines and tests, from Python. | [GoodData Python SDK](https://www.gooddata.ai/docs/python-sdk/latest/) | | REST API | GoodData is API-first: anything the tools do, you can do with the API from any language. | [API reference](https://www.gooddata.ai/docs/cloud/api-and-sdk/api/) | > **Beta:** > GoodData for VS Code and the GoodData CLI are beta features. They don't support saved views, alerts, and > schedules yet. Check the current state in the [GoodData for VS Code](https://www.gooddata.ai/docs/cloud/api-and-sdk/vs-code-extension/) > docs before you rely on them in production. To try it, follow [Get Started with GoodData for VS Code](https://www.gooddata.ai/docs/cloud/api-and-sdk/vs-code-extension/getting-started/). ## [Where GoodData.UI fits](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/analytics-as-code/#where-gooddataui-fits) Analytics as code decides *what* the workspace contains. GoodData.UI decides *where and how* your application shows it. The two meet at object IDs: the `id` in a YAML file is the ID your components load. ```tsx // The visualization's YAML file has `id: revenue_by_region` // The dashboard's YAML file has `id: sales_overview` ``` Metrics and attributes work the same way when you build a visualization in code with [`BarChart`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-charts/sdk-ui-charts.barchart/index.md), [`PivotTable`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-pivot/sdk-ui-pivot.pivottable/index.md), and the other components, where a workspace metric becomes a measure. To catch a renamed or deleted object before your users do, generate typed constants for the workspace with `@gooddata/catalog-export`, as the [quickstart](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/index.md) does: an ID that no longer exists then fails the TypeScript build instead of an empty chart at runtime. One way to wire the two together: 1. You change a metric in its YAML file and open a pull request. 2. CI runs `gd validate` on the files, and a reviewer approves the change. 3. On merge, CI runs `gd deploy` to update the workspace. 4. The application's build regenerates the catalog with `@gooddata/catalog-export` and compiles. If an ID the application uses is gone, the build fails here. 5. The application ships, and its components show the updated analytics. Keeping the analytics files and the application in one repository lets a single pull request change both: for example, a new metric and the chart that shows it. --- # Architecture overview > How GoodData.UI's layered architecture and the Analytical Backend Service Provider Interface let you build backend-agnostic analytics applications. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/architecture-overview/index.md GoodData.UI follows the principles of layered architecture and draws a clean line between the UI components and the backend server that calculates the data to render. The UI components integrate with a backend server through an interface called the **Analytical Backend**. The **Analytical Backend** is designed as a Service Provider Interface (SPI) with clearly defined contracts that you can implement to work even with your own arbitrary backend. For now, the full documentation of the SPI is provided only at the code level — see the `@gooddata/sdk-backend-spi` package. GoodData.UI comes with a full implementation of the Analytical Backend SPI for [GoodData Cloud](https://www.gooddata.ai/docs/cloud/) and [GoodData.CN](https://www.gooddata.ai/docs/cloud-native/latest/). GoodData.UI uses the **tiger** codename for these two GoodData products in all developer-facing artifacts and APIs. A codename outlasts product renames. > **GoodData Legacy:** > GoodData Legacy uses the **bear** codename in all developer-facing artifacts and APIs. This codename can only be found in GoodData.UI versions 9.x and lower. GoodData Legacy is no longer supported by GoodData.UI version 10 or newer. The **Analytical Backend** abstraction ultimately allows you to develop applications that can be backend-agnostic. ## [Packages](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/architecture-overview/#packages) GoodData.UI is published as a set of focused [npm packages](https://www.npmjs.com/org/gooddata) under the `@gooddata` scope. They are grouped below by the role each plays in the architecture. ### [Backend](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/architecture-overview/#backend) | Package | Contents | | :--- | :--- | | `@gooddata/sdk-backend-spi` | The Analytical Backend SPI — the abstraction interfaces you implement to integrate any backend | | `@gooddata/sdk-backend-tiger` | The Analytical Backend implementation for GoodData Cloud and GoodData.CN | | `@gooddata/api-client-tiger` | The REST API client for GoodData Cloud and GoodData.CN | ### [Model](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/architecture-overview/#model) | Package | Contents | | :--- | :--- | | `@gooddata/sdk-model` | The APIs and models used to construct inputs to executions and charts | ### [React UI components](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/architecture-overview/#react-ui-components) | Package | Contents | | :--- | :--- | | `@gooddata/sdk-ui` | The React infrastructure and headless components such as the Execute component | | `@gooddata/sdk-ui-charts` | The React components for all charts, plus the [`Repeater`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-charts/sdk-ui-charts.repeater/index.md) | | `@gooddata/sdk-ui-geo` | The React components for geo charts: [`GeoPushpinChart`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-geo/sdk-ui-geo.geopushpinchart/index.md) and [`GeoAreaChart`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-geo/sdk-ui-geo.geoareachart/index.md) | | `@gooddata/sdk-ui-pivot` | The React components for the pivot table: [`PivotTable`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-pivot/sdk-ui-pivot.pivottable/index.md) and [`PivotTableNext`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-pivot/sdk-ui-pivot.pivottablenext/index.md) | | `@gooddata/sdk-ui-filters` | The filtering components | | `@gooddata/sdk-ui-ext` | The components for rendering visualizations saved in your workspace, including [`InsightView`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-ext/sdk-ui-ext.insightview/index.md) | | `@gooddata/sdk-ui-dashboard` | The Dashboard component and its plugin API | | `@gooddata/sdk-ui-loaders` | Runtime component loaders, including the plugin-enabled dashboard loader | | `@gooddata/sdk-ui-kit` | Shared UI building-block components used across the SDK | | `@gooddata/sdk-ui-theme-provider` | The [`ThemeProvider`](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/sdk-ui-theme-provider/sdk-ui-theme-provider.themeprovider/index.md) component and theming support | | `@gooddata/sdk-ui-vis-commons` | Shared visualization utilities (colors, legends, config) used by the chart packages | | `@gooddata/sdk-ui-web-components` | Web Components (custom elements) that wrap GoodData.UI; distributed as a bundle rather than an npm install | | `@gooddata/sdk-ui-gen-ai` | The GenAI components, including the AI Assistant | | `@gooddata/sdk-ui-semantic-search` | The semantic search components | | `@gooddata/sdk-ui-application-header` | Application header components — workspace picker and notifications dropdown | | `@gooddata/sdk-ui-catalog` | The Analytics Catalog components | | `@gooddata/sdk-ui-all` | An umbrella package that re-exports the public API of `@gooddata/sdk-model`, `@gooddata/sdk-backend-spi`, `@gooddata/sdk-ui`, and the charts, geo, pivot, filters, and ext packages | ### [Embedding](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/architecture-overview/#embedding) | Package | Contents | | :--- | :--- | | `@gooddata/sdk-embedding` | APIs for embedding GoodData content and communicating with it | ### [Tooling](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/architecture-overview/#tooling) | Package | Contents | | :--- | :--- | | `@gooddata/app-toolkit` | A CLI for creating and maintaining GoodData web applications | | `@gooddata/catalog-export` | Exports a workspace's catalog of attributes and measures into code for type-safe references | | `@gooddata/plugin-toolkit` | Tools for creating and managing dashboard plugins | For the full list of public exports per package, see the [API Reference](https://www.gooddata.ai/docs/gooddata-ui/latest/api-reference/index.md). ## [Module format](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/architecture-overview/#module-format) The SDK ships as ES modules only — one build per package, with no parallel CommonJS output. A single module format means a single module graph, so your bundler sees every `@gooddata` import as the same kind of module and can shake unused code out of a package as large as `@gooddata/sdk-ui-dashboard`. It also removes the dual-package hazard, where a CommonJS and an ESM copy of the same package load side by side and their singletons stop matching. The cost is that your toolchain has to resolve ES modules and the `exports` field. See [ESM-only distribution](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/esm-only/index.md) for what each package exposes, the bundler, TypeScript, and Node requirements, and fixes for the common CommonJS errors. ## [Limitations](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/architecture-overview/#limitations) GoodData Cloud and GoodData.CN do not yet implement every capability specified on the Analytical Backend SPI. Before you rely on a given feature, review the [Limitations](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/limitations/index.md) page for the list of unsupported capabilities and the entity-referencing rules that affect GoodData.UI components. --- # Version lifecycle and compatibility > Check the lifecycle phase and support dates of each GoodData.UI major version for GoodData Cloud, GoodData.CN, and GoodData Legacy. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/compatibility/index.md Each GoodData.UI major version goes through lifecycle phases, and the phase decides which fixes and support the version gets. This page lists the phases and the dates for every major, separately for GoodData Cloud and GoodData.CN, and for GoodData Legacy. For the Node.js, React, TypeScript, and bundler versions that GoodData.UI needs, see [Platform compatibility](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/platform-compatibility/index.md). Use the latest version of GoodData.UI to get the newest features and security fixes. ## [Lifecycle phases](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/compatibility/#lifecycle-phases) Each version of GoodData.UI goes through the following phases: 1. **General Availability** (GA) - **When it starts:** When a major version is publicly released. To get notified about a new version, watch the [GoodData.UI releases on GitHub](https://github.com/gooddata/gooddata-ui-sdk/releases). - **What it means:** A version in GA is in active development and receives all new features and bug fixes, which are applied on top of the last minor version. 2. **End-of-Development** (EOD) - **When it starts:** When a newer major version is publicly released. - **What it means:** A version in EOD receives only security fixes (unless they can be resolved by a SemVer-compatible upgrade) and fixes for critical issues. Only production dependencies receive the security fixes. These fixes are applied on top of the last minor version. No new features are added. 3. **End-of-Support** (EOS) - **When it starts:** The date is defined by GoodData. - **What it means:** A version in EOS receives neither new features nor bug fixes, and no technical support is provided. The version is still available on npm, but don't use it. ## [GoodData Cloud and GoodData.CN](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/compatibility/#gooddata-cloud-and-gooddatacn) GoodData.UI is versioned and usually released together with GoodData Cloud. If you run GoodData.CN, keep both GoodData.UI and GoodData.CN on their newest versions. | Major version | Status | GA | EOD | EOS | | :------------ | :------------------ | :----------------- | :----------------- | :----------------- | | 11 | Generally available | September 22, 2025 | _Not yet defined_ | _Not yet defined_ | | 10 | End-of-Support | April 18, 2024 | September 22, 2025 | March 26, 2026 | | 9 | End-of-Support | July 27, 2023 | October 18, 2024 | September 22, 2025 | | 8 | End-of-Support | October 8, 2020 | July 27, 2023 | October 18, 2024 | | 7 | End-of-Support | May 21, 2019 | October 8, 2020 | March 31, 2022 | ## [GoodData Legacy](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/compatibility/#gooddata-legacy) GoodData Legacy (the deprecated GoodData Platform) is supported only by GoodData.UI version 9. Stay on version 9 and don't upgrade to version 10 or higher. From version 10 onwards, GoodData.UI supports only GoodData Cloud and GoodData.CN. | Major version | Status | GA | EOD | EOS | | :------------ | :------------------ | :-------------- | :---------------- | :---------------- | | 9 | Generally available | July 27, 2023 | _Not yet defined_ | _Not yet defined_ | | 8 | End-of-Support | October 8, 2020 | July 27, 2023 | October 18, 2024 | | 7 | End-of-Support | May 21, 2019 | October 8, 2020 | March 31, 2022 | --- # ESM-only distribution > The entry points GoodData.UI's ESM-only packages expose, the bundler, TypeScript, Node, and test runner requirements, and fixes for the common CommonJS errors. Source: https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/esm-only/index.md Every `@gooddata` runtime and CLI package is published as ES modules (ESM) only. Each package declares `"type": "module"`, ships its TypeScript declarations alongside the ESM build, and exposes its entry points through the `exports` field. There is no CommonJS build and no UMD build. This applies to the whole SDK: the runtime packages, the CLI tools (`@gooddata/app-toolkit`, `@gooddata/catalog-export`, `@gooddata/plugin-toolkit`), and the project templates they scaffold. ## [Package entry points](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/esm-only/#package-entry-points) A package's `exports` map is its supported surface. Most packages expose these subpaths: | Subpath | Contents | | :--- | :--- | | `.` | The package's public API — `import { BarChart } from "@gooddata/sdk-ui-charts";` | | `./internal` | Internal exports, on the packages that expose them. They may change or disappear at any time — see [API maturity](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/api-maturity/index.md) | | `./styles/css/*` and `./styles/scss/*` | Stylesheets, on the packages that ship compiled CSS — `import "@gooddata/sdk-ui-charts/styles/css/main.css";` | Roughly half the packages expose only `.`, and several add subpaths of their own: `@gooddata/sdk-ui-pivot` exposes `./next`, `@gooddata/sdk-ui-gen-ai` exposes `./context`, `@gooddata/sdk-ui-theme-provider` exposes `./node`, `@gooddata/api-client-tiger` exposes one `./endpoints/` entry per API area, and `@gooddata/sdk-ui-kit` uses `./styles/*` rather than `./styles/css/*`. The package's own `exports` map is the authoritative list. Paths the map does not declare do not resolve, even when the file ships inside the package tarball: ```ts // Won't resolve — not declared in the exports map import { BarChart } from "@gooddata/sdk-ui-charts/esm/charts/barChart/BarChart.js"; ``` ## [Toolchain requirements](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/esm-only/#toolchain-requirements) | Piece | Requirement | | :--- | :--- | | Bundler | Any bundler that resolves ES modules and the `exports` field: Vite, webpack 5, Rollup, esbuild, Parcel 2, or Next.js | | TypeScript | >= 5.8.3, with `module: NodeNext` and `moduleResolution: NodeNext` — or `moduleResolution: Bundler` paired with `module: ESNext` or `module: Preserve`. The legacy `node` (`node10`) resolution can't read `exports` and reports the package as missing | | Node.js | ^24.12.0 LTS, with `"type": "module"` in your `package.json` — or `.mjs` file extensions | | Test runner | Vitest runs ES modules natively. Jest needs its ESM mode configured | The project that `@gooddata/app-toolkit` scaffolds is already set up this way. Its `tsconfig.json`: ```json { "compilerOptions": { "target": "ES2022", "lib": ["ES2022", "DOM"], "module": "NodeNext", "moduleResolution": "NodeNext", "jsx": "react-jsx" } } ``` ## [CommonJS interoperability](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/esm-only/#commonjs-interoperability) A CommonJS module loads the SDK through a dynamic `import()`, which returns a promise for the module namespace: ```ts async function loadBackend() { const { tigerFactory } = await import("@gooddata/sdk-backend-tiger"); return tigerFactory(); } ``` > **Don't rely on require():** > Node.js 22.12 and later can `require()` some ES modules, but bundler-emitted CommonJS and transform-based test runners still can't. `import` and dynamic `import()` are the only reliable ways to load these packages. Config files that have to stay CommonJS keep the `.cjs` extension. The scaffolded app has none: its `vite.config.ts` is an ES module like the rest of the project — see the [project structure](https://www.gooddata.ai/docs/gooddata-ui/latest/getting-started/quickstart/index.md#look-around-the-project) in the Quick start guide. ## [Troubleshooting](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/esm-only/#troubleshooting) | Error | Cause | Fix | | :--- | :--- | :--- | | `Error [ERR_REQUIRE_ESM]: require() of ES Module …` | A CommonJS module called `require()` on an ESM package | Use `import`, or `await import()` from CommonJS | | `Cannot find module '@gooddata/sdk-ui' or its corresponding type declarations` | TypeScript `moduleResolution` is `node` or `node10`, which ignores the `exports` field | Set `module` and `moduleResolution` to `NodeNext` | | `Cannot use import statement outside a module` | Jest, or another runner, is transforming ES modules as CommonJS | Configure Jest for ESM, or switch to Vitest | | webpack `Module not found: Error: Can't resolve './x'` | ES modules require fully specified relative imports | Keep the `.js` extension on relative imports, or set `resolve.extensionAlias` in your webpack config | ## [Web Components](https://www.gooddata.ai/docs/gooddata-ui/latest/integrations/platform/esm-only/#web-components) `@gooddata/sdk-ui-web-components` is not published to npm. It loads from your GoodData server as a `