Creating data providers

How to become a data source for Canva's data-driven features.

Canva has a number of data-driven features, such as Bulk Create and charts. These features help users streamline their design workflows, create stunning visualizations, and more.

Users can import data from a number of data sources, such as CSV files, and it's also possible for apps to become data sources.

  • Share interesting and useful datasets with the world.
  • Create trusted data sources that reduce errors and misinformation.
  • Save hours of effort by generating designs from a dataset.

Here are some examples of apps that act as data sources:

  • An app for a real estate agency that exposes property data. The real estate agents could then use that data to generate designs for the listings.
  • An app for a not-for-profit organization that exposes data relating to their impact. The staff could then use that data to keep the data in their messaging aligned.
  • Provider - A data source that Canva features or apps can import data from.
  • Consumer - A Canva feature or app that imports data from a provider. The consumer can then use that data however it wants — for example, to render a chart.
  • Data table - The data structure that providers must use when exposing data. By conforming to this structure, all providers are automatically compatible with all consumers.

A data table shares a number of similarities with spreadsheets:

  • They have columns
  • Each column has a name
  • Each column has a list of values

This is an example of a data table that contains a list of dog breeds:

NameCountryWeight
Golden retrieverScotland35
Labrador retrieverUnited Kingdom35
Flat-coated retrieverEngland30

In code, a data table is a plain JavaScript object with a name and an array of columns:

{
"name": "Dog breeds",
"columns": []
}

A column is a plain JavaScript object with a data type, a name, and an array of values:

{
"type": "string",
"name": "Name",
"values": ["Golden retriever", "Labrador retriever", "Flat-coated retriever"]
}

The type property can be set to any of the following values:

TypeExample
"string""Hello world"
"boolean"true
"number"42
"date"new Date()

The data type of the values must match the data type specified in the type property.

You can include undefined values in the values array. This creates an empty cell and is the only instance where the value's data type doesn't have to match the data type in the type property.

  1. Log in to the Developer Portal.
  2. Navigate to an app via the Your apps page.
  3. Enable Data Provider.

Import the DataTable type:

import { DataTable } from "@canva/preview/data";

Create an object that conforms to the DataTable type:

const DATA_TABLE: DataTable = {
name: "Dog breeds",
columns: [
{
type: "string",
name: "name",
values: [
"Golden retriever",
"Labrador retriever",
"Flat-coated retriever",
"Goldendoodle",
],
},
{
type: "string",
name: "country",
values: ["Scotland", "United Kingdom", "England", "Australia"],
},
{ type: "number", name: "weight", values: [35, 35, 30, 25] },
{
type: "boolean",
name: "isPureBreed",
values: [true, true, true, false],
},
],
};

In this case, the data table is hard-coded, but an actual app would either:

  • Fetch the data from a backend
  • Create the data at runtime

When the app loads, register a callback with the onSelectDataTable method:

import { onSelectDataTable } from "@canva/preview/data";
React.useEffect(() => {
onSelectDataTable(async (options) => {
options.selectDataTable(DATA_TABLE);
});
}, []);

This callback receives a selectDataTable method. This method accepts a data table as its only argument and sends this data table to Canva.

Because the app doesn't have a UI, the App component can return undefined:

export function App() {
React.useEffect(() => {
onSelectDataTable((options) => {
options.selectDataTable(DATA_TABLE);
});
}, []);
}

In the App component, return a UI that lets the user select or configure the data to send to Canva — the exact UI will depend on the shape and requirements of the underlying dataset.

In the following code sample, the user is shown a list of cities:

import React from "react";
import { DataTable, onSelectDataTable } from "@canva/preview/data";
export function App() {
return (
<div>
<div>Choose a location</div>
<div>
<button>Brisbane</button>
</div>
<div>
<button>Melbourne</button>
</div>
<div>
<button>Sydney</button>
</div>
</div>
);
}

Import the SelectDataTableRequest type:

import {
DataTable,
SelectDataTableRequest,
onSelectDataTable,
} from "@canva/preview/data";

This type represents the callback that's registered with the onSelectDataTable method.

Use the useState hook to store the callback:

const [callback, setCallback] = React.useState<
SelectDataTableRequest | undefined
>();
React.useEffect(() => {
onSelectDataTable((options) => {
setCallback(options);
});
}, []);

This makes it easier to call the selectDataTable method in response to UI-driven events, such as a button click.

When a user has selected or configured the data to send to Canva, call the selectDataTable method and pass through the relevant data table.

In the following code sample, the user can click a button to get a data table from an array:

import React from "react";
import {
DataTable,
SelectDataTableRequest,
onSelectDataTable,
} from "@canva/preview/data";
const DATA_TABLES: DataTable[] = [
{
name: "Brisbane",
columns: [
{
type: "number",
name: "Price",
values: [100000, 200000, 300000],
},
],
},
{
name: "Melbourne",
columns: [
{
type: "number",
name: "Price",
values: [100000, 200000, 300000],
},
],
},
{
name: "Sydney",
columns: [
{
type: "number",
name: "Price",
values: [100000, 200000, 300000],
},
],
},
];
export function App() {
const [callback, setCallback] = React.useState<
SelectDataTableRequest | undefined
>();
React.useEffect(() => {
onSelectDataTable((options) => {
setCallback(options);
});
}, []);
function getDataForCity(name: string) {
const table = DATA_TABLES.find((table) => table.name === name);
if (!table) {
throw new Error(`Can't find table with name: ${name}`);
}
if (!callback) {
throw new Error(`onSelectDataTable callback is not defined`);
}
callback.selectDataTable(table);
}
return (
<div>
<div>Choose a location</div>
<div>
<button
onClick={() => {
getDataForCity("Brisbane");
}}
>
Brisbane
</button>
</div>
<div>
<button
onClick={() => {
getDataForCity("Melbourne");
}}
>
Melbourne
</button>
</div>
<div>
<button
onClick={() => {
getDataForCity("Sydney");
}}
>
Sydney
</button>
</div>
</div>
);
}

If a provider renders a UI, the UI can be tested the same as any other app — by opening the app in the Developer Portal and clicking the Preview button.

If you want to test how a provider interacts with a consumer, the only option is to test it via Canva's Bulk Create feature. In the future, as other consumers are released, more options will become available.

  1. Create a design in Canva.
  2. In the object panel, click Apps.
  3. Select Bulk Create.
  4. Click More data sources.
  5. Select a provider.
  1. Add a text element to the design.
  2. Right-click the element.
  3. Select Assign data.
  4. In the dropdown that appears, select one or more columns from the data table.
  5. In the object panel, click Continue.
  6. Select one or more rows of data.
  7. Click Generate.
import React from "react";
import { DataTable, onSelectDataTable } from "@canva/preview/data";
const DATA_TABLE: DataTable = {
name: "Dog breeds",
columns: [
{
type: "string",
name: "name",
values: [
"Golden retriever",
"Labrador retriever",
"Flat-coated retriever",
"Goldendoodle",
],
},
{
type: "string",
name: "country",
values: ["Scotland", "United Kingdom", "England", "Australia"],
},
{ type: "number", name: "weight", values: [35, 35, 30, 25] },
{
type: "boolean",
name: "isPureBreed",
values: [true, true, true, false],
},
],
};
export function App() {
React.useEffect(() => {
onSelectDataTable((options) => {
options.selectDataTable(DATA_TABLE);
});
}, []);
}
import React from "react";
import {
DataTable,
SelectDataTableRequest,
onSelectDataTable,
} from "@canva/preview/data";
const DATA_TABLES: DataTable[] = [
{
name: "Brisbane",
columns: [
{
type: "number",
name: "Price",
values: [100000, 200000, 300000],
},
],
},
{
name: "Melbourne",
columns: [
{
type: "number",
name: "Price",
values: [100000, 200000, 300000],
},
],
},
{
name: "Sydney",
columns: [
{
type: "number",
name: "Price",
values: [100000, 200000, 300000],
},
],
},
];
export function App() {
const [callback, setCallback] = React.useState<
SelectDataTableRequest | undefined
>();
React.useEffect(() => {
onSelectDataTable((options) => {
setCallback(options);
});
}, []);
function getDataForCity(name: string) {
const table = DATA_TABLES.find((table) => table.name === name);
if (!table) {
throw new Error(`Can't find table with name: ${name}`);
}
if (!callback) {
throw new Error(`onSelectDataTable callback is not defined`);
}
callback.selectDataTable(table);
}
return (
<div>
<div>Choose a location</div>
<div>
<button
onClick={() => {
getDataForCity("Brisbane");
}}
>
Brisbane
</button>
</div>
<div>
<button
onClick={() => {
getDataForCity("Melbourne");
}}
>
Melbourne
</button>
</div>
<div>
<button
onClick={() => {
getDataForCity("Sydney");
}}
>
Sydney
</button>
</div>
</div>
);
}