Examples
App elements
Assets and media
Fundamentals
Intents
Design interaction
Drag and drop
Design elements
Localization
Content replacement
Data connector intent
Data connector integration for external data sources.
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 data_connector_intentSHELL -
Click the Preview URL link shown in the terminal to open the example in the Canva editor.
Example app source code
// For usage information, see the README.md file.import type {DataTable,DataTableImageUpload,DataTableVideoUpload,GetDataTableRequest,} from "@canva/intents/data";// Available filter options for real estate project sales stagesexport const saleStageOptions: string[] = ["Initial Release Stage","Construction Stage","Final Release Stage",];// Configuration object that defines the structure of a data source queryexport type RealEstateDataConfig = {selectedStageFilter?: string[];};// Fetches data from the mock API and transforms it into Canva's DataTable formatexport const getRealEstateData = async (request: GetDataTableRequest,): Promise<DataTable> => {return new Promise((resolve) => {const { dataSourceRef, limit } = request;if (dataSourceRef != null) {// Parse the saved data source configuration from the requestconst dataRef = JSON.parse(dataSourceRef?.source) as RealEstateDataConfig;// Use selected stages from config, or default to all stages if none selectedconst selectedStages = dataRef.selectedStageFilter?.length? dataRef.selectedStageFilter: saleStageOptions;// Fetch mock project dataconst projects = getProjects();// Transform raw project data to Canva's DataTable formatconst dataTable = transformToDataTable(projects, selectedStages);// Apply row limit as specified by Canva's data connector constraintsdataTable.rows = dataTable.rows.slice(0, limit.row);resolve(dataTable);}});};// Structure representing a real estate project from the mock APIinterface RealEstateProject {name: string;initialReleaseStage: number;constructionStage: number;finalReleaseStage: number;media: (DataTableImageUpload | DataTableVideoUpload)[];}/*** Sample data for real estate projects.* Each project has a name, sales stage values, and media assets.*/const getProjects = (): RealEstateProject[] => [{name: "The Kensington",initialReleaseStage: getRandomSalesValue(),constructionStage: getRandomSalesValue(),finalReleaseStage: getRandomSalesValue(),media: staticMediaData,},{name: "Horizon Hurstville",initialReleaseStage: getRandomSalesValue(),constructionStage: getRandomSalesValue(),finalReleaseStage: getRandomSalesValue(),media: staticMediaData,},{name: "Sterling Lane Cove",initialReleaseStage: getRandomSalesValue(),constructionStage: getRandomSalesValue(),finalReleaseStage: getRandomSalesValue(),media: staticMediaData,},{name: "Surry Hills Village",initialReleaseStage: getRandomSalesValue(),constructionStage: getRandomSalesValue(),finalReleaseStage: getRandomSalesValue(),media: staticMediaData,},{name: "Willoughby Grounds",initialReleaseStage: getRandomSalesValue(),constructionStage: getRandomSalesValue(),finalReleaseStage: getRandomSalesValue(),media: staticMediaData,},{name: "Marque Rockdale",initialReleaseStage: getRandomSalesValue(),constructionStage: getRandomSalesValue(),finalReleaseStage: getRandomSalesValue(),media: staticMediaData,},{name: "Atrium The Retreat",initialReleaseStage: getRandomSalesValue(),constructionStage: getRandomSalesValue(),finalReleaseStage: getRandomSalesValue(),media: staticMediaData,},{name: "Aura by Aqualand",initialReleaseStage: getRandomSalesValue(),constructionStage: getRandomSalesValue(),finalReleaseStage: getRandomSalesValue(),media: staticMediaData,},];// Converts the mock API project data into Canva's DataTable format with dynamic column configurationconst transformToDataTable = (projects: RealEstateProject[],selectedStages: string[],): DataTable => {// Define column structure based on user's selected stagesconst columnConfigs = [{ name: "Project", type: "string" as const },...(selectedStages.includes("Initial Release Stage")? [{ name: "Initial Release Stage", type: "number" as const }]: []),...(selectedStages.includes("Construction Stage")? [{ name: "Construction Stage", type: "number" as const }]: []),...(selectedStages.includes("Final Release Stage")? [{ name: "Final Release Stage", type: "number" as const }]: []),{ name: "Media", type: "media" as const },];// Generate table rows with data cells matching the column structureconst rows = projects.map((project) => ({cells: [{ type: "string" as const, value: project.name },...(selectedStages.includes("Initial Release Stage")? [{ type: "number" as const, value: project.initialReleaseStage }]: []),...(selectedStages.includes("Construction Stage")? [{ type: "number" as const, value: project.constructionStage }]: []),...(selectedStages.includes("Final Release Stage")? [{ type: "number" as const, value: project.finalReleaseStage }]: []),{ type: "media" as const, value: project.media },],}));return { columnConfigs, rows };};// Static media assets used for demonstration purposes in all projectsconst staticMediaData: (DataTableImageUpload | DataTableVideoUpload)[] = [{type: "video_upload",mimeType: "video/mp4",url: "https://www.canva.dev/example-assets/video-import/video.mp4",thumbnailImageUrl:"https://www.canva.dev/example-assets/video-import/thumbnail-image.jpg",thumbnailVideoUrl:"https://www.canva.dev/example-assets/video-import/thumbnail-video.mp4",width: 405,height: 720,aiDisclosure: "none",},{type: "image_upload",mimeType: "image/jpeg",url: "https://www.canva.dev/example-assets/image-import/image.jpg",thumbnailUrl:"https://www.canva.dev/example-assets/image-import/thumbnail.jpg",width: 540,height: 720,aiDisclosure: "none",},];// Generates random sales values for demonstration purposesfunction getRandomSalesValue(): number {const min = 10;const max = 100;return Math.floor(Math.random() * (max - min + 1)) + min;}
TYPESCRIPT
// For usage information, see the README.md file.import type {RenderSelectionUiRequest,GetDataTableRequest,GetDataTableResponse,} from "@canva/intents/data";import { prepareDataConnector } from "@canva/intents/data";import { Alert, AppUiProvider } from "@canva/app-ui-kit";import { createRoot } from "react-dom/client";import { SelectionUI } from "./selection_ui";import "@canva/app-ui-kit/styles.css";import { getRealEstateData } from "./data";const root = createRoot(document.getElementById("root") as Element);// Configure the data connector intent with required callbacksprepareDataConnector({/*** Gets structured data from an external source.** This action is called in two scenarios:** - During data selection to preview data before import (when {@link RenderSelectionUiRequest.updateDataRef} is called).* - When refreshing previously imported data (when the user requests an update).** @param request - Parameters for the data fetching operation.* @returns A promise resolving to either a successful result with data or an error.*/getDataTable: async (request: GetDataTableRequest,): Promise<GetDataTableResponse> => {try {const dataTable = await getRealEstateData(request);return {status: "completed",dataTable,metadata: {description:"Sydney construction project sales in each release stage",providerInfo: { name: "Demo Sales API" },},};} catch {return {status: "app_error",message: "Failed to process data request",};}},/*** Renders a UI component for selecting and configuring data from external sources.* This UI should allow users to browse data sources, apply filters, and select data.* When selection is complete, the implementation must call the `updateDataRef`* callback provided in the params to preview and confirm the data selection.** @param request - parameters that provide context and configuration for the data selection UI.* Contains invocation context, size limits, and the updateDataRef callback*/renderSelectionUi: async (request: RenderSelectionUiRequest) => {root.render(<AppUiProvider><SelectionUI {...request} /></AppUiProvider>,);},});// Fallback message displayed when the data connector intent is not enabled.// Remove this once your app is correctly configured in the developer portal.root.render(<AppUiProvider><Alert tone="critical">If you're seeing this, you need to turn on the data connector intent inthe developer portal for this app.</Alert></AppUiProvider>,);
TYPESCRIPT
// For usage information, see the README.md file.import type { RenderSelectionUiRequest } from "@canva/intents/data";import {Alert,Button,Rows,Text,CheckboxGroup,FormField,Title,} from "@canva/app-ui-kit";import { useEffect, useState } from "react";import * as styles from "styles/components.css";import { saleStageOptions, type RealEstateDataConfig } from "./data";export const SelectionUI = (request: RenderSelectionUiRequest) => {const [selectedStageFilter, setSelectedStageFilter] = useState<string[]>();const [error, setError] = useState<string | null>(null);const [loading, setLoading] = useState(false);const [success, setSuccess] = useState(false);// Handle different invocation contexts (data selection, errors, outdated references)// This determines how to initialize the UI based on why the data connector was openeduseEffect(() => {const { reason } = request.invocationContext;switch (reason) {case "data_selection":// Pre-populate UI with existing data source configuration if availableif (request.invocationContext.dataSourceRef) {try {const savedParams = JSON.parse(request.invocationContext.dataSourceRef.source,) as RealEstateDataConfig;setSelectedStageFilter(savedParams.selectedStageFilter || []);} catch {setError("Failed to load saved selection");}}break;case "outdated_source_ref":// The data source reference stored in Canva is no longer validsetError("Your previously selected data is no longer available. Please make a new selection.",);break;case "app_error":// Display error message from previous data fetch attemptsetError(request.invocationContext.message ||"An error occurred with your data",);break;default:break;}}, [request.invocationContext]);// Sends the selected data configuration to Canva and triggers data preview// This calls the getDataTable function with the current selectionconst loadData = async () => {setLoading(true);setError(null);setSuccess(false);try {// Call Canva's updateDataRef with the current configurationconst result = await request.updateDataRef({source: JSON.stringify({ selectedStageFilter }),title: "Sydney construction project sales in each release stage",});if (result.status === "completed") {setSuccess(true);} else {setError(result.status === "app_error" && "message" in result? result.message || "An error occurred": `Error: ${result.status}`,);}} catch {setError("Failed to update data");} finally {setLoading(false);}};return (<div className={styles.scrollContainer}><Rows spacing="2u"><Title size="large">Project Sales</Title>{error && <Alert tone="critical" title={error} />}{success && (<Alert tone="positive" title="Data preview loaded successfully!" />)}<Text variant="bold">Sydney construction project sales in each release stage</Text><Rows spacing="1u"><FormFieldlabel="Release Stage"control={(props) => (<CheckboxGroup{...props}value={selectedStageFilter}options={saleStageOptions.map((stage) => {return {label: stage,value: stage,};})}onChange={setSelectedStageFilter}/>)}/><Buttonvariant="primary"onClick={loadData}loading={loading}stretch>Load data</Button></Rows></Rows></div>);};
TYPESCRIPT
# Data connector intentThis example demonstrates how to build a data connector intent app that allows users to import structured data from external sources into Canva designs. The app shows how to implement both the data fetching logic and the selection UI for filtering and configuring data imports.For API reference docs and instructions on running this example, see: <https://www.canva.dev/docs/apps/examples/data-connector-intent/>.NOTE: This example differs from what is expected for public apps to pass a Canva review:- **Static assets**: This example uses static Canva-hosted URLs for media content. Production apps should use CDN/hosting services and implement the `upload` function from `@canva/asset` package for real media uploads.- **Localization**: Text content is hardcoded in English. Production apps require proper internationalization using the `@canva/app-i18n-kit` package for multi-language support.- **Data validation**: The example assumes well-formed data responses. Production apps should implement robust data validation and sanitization.- **API integration**: This example uses mock data. Production apps need to implement proper API authentication, rate limiting, and error handling for external data sources.
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)