Examples
App elements
Assets and media
Fundamentals
Intents
Design interaction
Drag and drop
Design elements
Localization
Content replacement
Request data table
Request a data table from the app's own Data Connector using requestDataTable.
Running this example
To run this example locally:
-
If you haven't already, create a new app in the Developer Portal(opens in a new tab or window). For more information, refer to our Quickstart guide.
-
In your app's configuration on the Developer Portal(opens in a new tab or window), ensure the "Development URL" is set to
http://localhost:8080. -
Clone the starter kit:
git clone https://github.com/canva-sdks/canva-apps-sdk-starter-kit.gitcd canva-apps-sdk-starter-kitSHELL -
Install dependencies:
npm installSHELL -
Run the example:
npm run start request_data_tableSHELL -
Click the Preview URL link shown in the terminal to open the example in the Canva editor.
Example app source code
{"$schema": "https://www.canva.dev/schemas/app/v1/manifest-schema.json","manifest_schema_version": 1,"runtime": {"permissions": [{"name": "canva:design:content:read","type": "mandatory"},{"name": "canva:design:content:write","type": "mandatory"}]},"intent": {"data_connector": {"enrolled": true},"design_editor": {"enrolled": true}}}
JSON
// For usage information, see the README.md file.import type {ColumnConfig,DataTable,DataTableRow,} from "@canva/intents/data";export type TeamMember = {name: string;role: string;location: string;};// A small hardcoded dataset. A real Data Connector would fetch data like this// from an external API, using the request's `dataSourceRef` to identify what// to fetch.export const TEAM_MEMBERS: TeamMember[] = [{ name: "Ana Silva", role: "Product Designer", location: "Sydney" },{ name: "Wei Chen", role: "Engineer", location: "Singapore" },{ name: "Priya Nair", role: "Engineer", location: "Bengaluru" },{ name: "Tom Baker", role: "Marketing Lead", location: "London" },{ name: "Sofia Reyes", role: "Customer Success", location: "Austin" },];const COLUMN_CONFIGS: ColumnConfig[] = [{ name: "Name", type: "string" },{ name: "Role", type: "string" },{ name: "Location", type: "string" },];export function buildDataTable(members: TeamMember[]): DataTable {const rows: DataTableRow[] = members.map((member) => ({cells: [{ type: "string", value: member.name },{ type: "string", value: member.role },{ type: "string", value: member.location },],}));return { columnConfigs: COLUMN_CONFIGS, rows };}
TYPESCRIPT
// For usage information, see the README.md file.// This root index file contains the prepare function calls that initialize each intent.// Each intent entrypoint is responsible for exporting the intent contract implementation.import { prepareDataConnector } from "@canva/intents/data";import { prepareDesignEditor } from "@canva/intents/design";import dataConnector from "./intents/data_connector";import designEditor from "./intents/design_editor";prepareDataConnector(dataConnector);prepareDesignEditor(designEditor);
TYPESCRIPT
// For usage information, see the README.md file.import type {DataConnectorIntent,GetDataTableRequest,GetDataTableResponse,RenderSelectionUiRequest,} from "@canva/intents/data";import { AppUiProvider } from "@canva/app-ui-kit";import "@canva/app-ui-kit/styles.css";import { createRoot } from "react-dom/client";import { buildDataTable, TEAM_MEMBERS } from "../../data";import { SelectionUI } from "./selection_ui";/*** Returns the app's fixed team-directory dataset.** A real Data Connector would use `request.dataSourceRef.source` to identify* which data to fetch from an external source.*/async function getDataTable(request: GetDataTableRequest,): Promise<GetDataTableResponse> {return {status: "completed",dataTable: buildDataTable(TEAM_MEMBERS),metadata: {description: "Team directory",providerInfo: { name: "Team Directory" },},};}function renderSelectionUi(request: RenderSelectionUiRequest) {const root = createRoot(document.getElementById("root") as Element);root.render(<AppUiProvider><SelectionUI {...request} /></AppUiProvider>,);}const dataConnector: DataConnectorIntent = { getDataTable, renderSelectionUi };export default dataConnector;
TYPESCRIPT
// For usage information, see the README.md file.import { Alert, Button, Rows, Text, Title } from "@canva/app-ui-kit";import type { RenderSelectionUiRequest } from "@canva/intents/data";import { useState } from "react";import * as styles from "styles/components.css";import { TEAM_MEMBERS } from "../../data";const TEAM_DATA_SOURCE_REF = {source: "team_members",title: "Team members",};export function SelectionUI({ updateDataRef }: RenderSelectionUiRequest) {const [isLoading, setIsLoading] = useState(false);const [error, setError] = useState<string | null>(null);const handleLoadData = async () => {setIsLoading(true);setError(null);const result = await updateDataRef(TEAM_DATA_SOURCE_REF);if (result.status !== "completed") {setError("Failed to load data.");}setIsLoading(false);};return (<div className={styles.scrollContainer}><Rows spacing="2u"><Title>Team directory</Title><Text>A fixed set of {TEAM_MEMBERS.length} team members. Loading them makesthis table available to `requestDataTable` in the design editor.</Text><Buttonvariant="primary"onClick={handleLoadData}loading={isLoading}stretch>Load team data</Button>{error && <Alert tone="critical">{error}</Alert>}</Rows></div>);}
TYPESCRIPT
// For usage information, see the README.md file.import { Alert, Button, Rows, Text, Title } from "@canva/app-ui-kit";import type { DataTable } from "@canva/intents/data";import { requestDataTable } from "@canva/intents/data";import { useState } from "react";import * as styles from "styles/components.css";export function App() {const [dataTable, setDataTable] = useState<DataTable | null>(null);const [isLoading, setIsLoading] = useState(false);const [error, setError] = useState<string | null>(null);const handleGetDataTable = async () => {setIsLoading(true);setError(null);try {// Opens the selection UI for this app's own Data Connector (see// intents/data_connector/index.tsx) and resolves with the DataTable the// user loads there.const table = await requestDataTable({withDataConnector: "self",dataSelectionDisplay: "show",});setDataTable(table);} catch (err) {setError(err instanceof Error ? err.message : "Failed to get data.");} finally {setIsLoading(false);}};return (<div className={styles.scrollContainer}><Rows spacing="2u"><Title>Request data table</Title><Text>Calls requestDataTable to open this app's own Data Connector and fetchthe table the user loads.</Text><Buttonvariant="primary"onClick={handleGetDataTable}loading={isLoading}stretch>Get data</Button>{error && <Alert tone="critical">{error}</Alert>}{dataTable && <DataTablePreview dataTable={dataTable} />}</Rows></div>);}function DataTablePreview({ dataTable }: { dataTable: DataTable }) {const columnNames = dataTable.columnConfigs?.map((c) => c.name ?? "") ?? [];return (<Rows spacing="1u"><Text size="small" tone="secondary">{dataTable.rows.length} row{dataTable.rows.length !== 1 ? "s" : ""}{" "}returned</Text><div style={{ overflowX: "auto" }}><table style={{ borderCollapse: "collapse", width: "100%" }}>{columnNames.length > 0 && (<thead><tr>{columnNames.map((name, i) => (<thkey={i}style={{textAlign: "left",padding: "4px 8px",borderBottom: "1px solid var(--ui-kit-border-standard)",}}>{name}</th>))}</tr></thead>)}<tbody>{dataTable.rows.map((row, rowIndex) => (<tr key={rowIndex}>{row.cells.map((cell, cellIndex) => (<tdkey={cellIndex}style={{padding: "4px 8px",borderBottom: "1px solid var(--ui-kit-border-standard)",}}>{cell.type === "string" ? (cell.value ?? "") : ""}</td>))}</tr>))}</tbody></table></div></Rows>);}
TYPESCRIPT
// For usage information, see the README.md file.import "@canva/app-ui-kit/styles.css";import type { DesignEditorIntent } from "@canva/intents/design";import { AppUiProvider } from "@canva/app-ui-kit";import { createRoot } from "react-dom/client";import { App } from "./app";async function render() {const root = createRoot(document.getElementById("root") as Element);root.render(<AppUiProvider><App /></AppUiProvider>,);}const designEditor: DesignEditorIntent = { render };export default designEditor;// Hot Module Replacement for development (automatically reloads the app when changes are made)if (module.hot) {module.hot.accept("./app", render);}
TYPESCRIPT
# Request data tableThis example demonstrates how to use `requestDataTable` from `@canva/intents/data` tofetch a `DataTable` from an app's own Data Connector directly from the design editorintent, without the user having to leave the design editor experience.The app implements two intents:- **Data Connector** (`intents/data_connector`) — exposes a small, fixed team-directorydataset as a Data Connector data source.- **Design Editor** (`intents/design_editor`) — a single "Get data" button that calls`requestDataTable({ withDataConnector: "self", dataSelectionDisplay: "show" })`, whichopens the Data Connector's selection UI and resolves with the `DataTable` the userloads, then renders it as a simple table.For API reference docs and instructions on running this example, see:<https://www.canva.dev/docs/apps/examples/request-data-table/>.Related examples: see `intents/data_connector_intent` for a fuller Data Connectorimplementation (search, filtering, multiple data sources), and`intents/implement_multiple_intents` for the general pattern of combining multipleintents in one app.NOTE: This example differs from what is expected for public apps to pass a Canva review:- **Preview/beta API**: `requestDataTable` is a `@beta` API. Check the latest APIstatus in the docs before relying on it in a production app.- **Static/hardcoded data**: The Data Connector always returns the same fixed dataset.Production apps should use `request.dataSourceRef.source` to identify and fetch theactual data requested from an external source.- **Localization**: Text content is hardcoded in English. Production apps requireproper internationalization using the `@canva/app-i18n-kit` package for multi-languagesupport.- **Error handling**: Error handling is simplified for demonstration. Production appsmust implement comprehensive error handling with clear user feedback and gracefulfailure modes.
MARKDOWN
API reference
Need help?
- Join our Community Forum(opens in a new tab or window)
- Report issues with this example on GitHub(opens in a new tab or window)