Autofill

Match data fields and autofill the current design using a hardcoded data table.

Running this example

To run this example locally:

  1. 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.

  2. 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.

  3. Clone the starter kit:

    git clone https://github.com/canva-sdks/canva-apps-sdk-starter-kit.git
    cd canva-apps-sdk-starter-kit
    SHELL
  4. Install dependencies:

    npm install
    SHELL
  5. Run the example:

    npm run start autofill
    SHELL
  6. 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 {
Alert,
Button,
LoadingIndicator,
Rows,
Text,
Title,
} from "@canva/app-ui-kit";
import type { DataField } from "@canva/design";
import {
autofillDesign,
getDesignMetadata,
requestDataFieldMatching,
} from "@canva/design";
import { useCallback, useEffect, useState } from "react";
import * as styles from "styles/components.css";
import { buildDataTable, TEAM_MEMBERS } from "./data";
type Message = {
text: string;
tone: "positive" | "critical" | "warn";
};
export function App() {
const [datasetFields, setDatasetFields] = useState<DataField[]>([]);
const [isLoadingDataset, setIsLoadingDataset] = useState(true);
const [isMatching, setIsMatching] = useState(false);
const [isAutofilling, setIsAutofilling] = useState(false);
const [message, setMessage] = useState<Message | null>(null);
// Fields tagged for Autofill can change while this panel stays open (the user
// tags/untags fields, or runs field matching), so re-read them rather than
// trusting a one-time snapshot.
const refreshDatasetFields = useCallback(async () => {
try {
const metadata = await getDesignMetadata();
setDatasetFields(metadata.dataset ?? []);
} finally {
setIsLoadingDataset(false);
}
}, []);
useEffect(() => {
void refreshDatasetFields();
}, [refreshDatasetFields]);
const handleMatchFields = async () => {
setIsMatching(true);
setMessage(null);
try {
// Offers this example's field labels as sample data the user can match
// against tagged elements in the design.
await requestDataFieldMatching({
sampleData: buildDataTable(TEAM_MEMBERS),
});
await refreshDatasetFields();
setMessage({ text: "Field matching updated.", tone: "positive" });
} catch (err) {
setMessage({
text: err instanceof Error ? err.message : "Field matching failed.",
tone: "critical",
});
} finally {
setIsMatching(false);
}
};
const handleAutofill = async () => {
setIsAutofilling(true);
setMessage(null);
try {
const response = await autofillDesign({
dataTable: buildDataTable(TEAM_MEMBERS),
});
if (response.status === "success") {
setMessage({
text: "Design autofilled successfully!",
tone: "positive",
});
} else {
setMessage({
text: "No fields in this design are tagged for Autofill. Tag fields using the design's Autofill panel, then try again.",
tone: "warn",
});
}
} catch (err) {
setMessage({
text: err instanceof Error ? err.message : "Autofill failed.",
tone: "critical",
});
} finally {
setIsAutofilling(false);
}
};
return (
<div className={styles.scrollContainer}>
<Rows spacing="2u">
<Title>Autofill</Title>
<Text>
Match this app's data fields against the design's tagged elements,
then autofill the design with a hardcoded data table.
</Text>
{isLoadingDataset ? (
<LoadingIndicator size="small" />
) : datasetFields.length === 0 ? (
<Text tone="secondary">
No fields are tagged for Autofill in this design yet.
</Text>
) : (
<Rows spacing="0.5u">
<Text size="small" tone="secondary">
Tagged fields:
</Text>
{datasetFields.map((field) => (
<Text key={field.label} size="small">
{`${field.label} (${field.type})`}
</Text>
))}
</Rows>
)}
<Button
variant="secondary"
onClick={handleMatchFields}
loading={isMatching}
stretch
>
Match data fields
</Button>
<Button
variant="primary"
onClick={handleAutofill}
loading={isAutofilling}
stretch
>
Autofill design
</Button>
{message && <Alert tone={message.tone}>{message.text}</Alert>}
</Rows>
</div>
);
}
TYPESCRIPT
// 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 standing in for data you'd otherwise fetch from a
// Data Connector (see the intents/request_data_table example) or an external API.
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" },
];
// `autofillDesign` and `requestDataFieldMatching` accept the same `DataTable`
// shape as the Data Connector intent, so it's imported from `@canva/intents/data`
// rather than redeclared here.
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.
import { AppUiProvider } from "@canva/app-ui-kit";
import { createRoot } from "react-dom/client";
import { App } from "./app";
import "@canva/app-ui-kit/styles.css";
import type { DesignEditorIntent } from "@canva/intents/design";
import { prepareDesignEditor } from "@canva/intents/design";
async function render() {
const root = createRoot(document.getElementById("root") as Element);
root.render(
<AppUiProvider>
<App />
</AppUiProvider>,
);
}
const designEditor: DesignEditorIntent = { render };
prepareDesignEditor(designEditor);
// Hot Module Replacement for development (automatically reloads the app when changes are made)
if (module.hot) {
module.hot.accept("./app", render);
}
TYPESCRIPT
# Autofill
This example demonstrates how to match data fields and autofill the current
design using `requestDataFieldMatching` and `autofillDesign` from
`@canva/design`, against a hardcoded data table (no Data Connector needed).
The hardcoded table is typed with the `DataTable` type from `@canva/intents/data`
— the same `DataTable` shape a Data Connector's `getDataTable` would return.
The app implements a single **Design Editor** intent. It reads the design's
currently tagged fields with `getDesignMetadata`. "Match data fields" calls
`requestDataFieldMatching` with a small hardcoded sample table so the user can
match this app's field labels ("Name", "Role", "Location") against tagged
elements in the design. "Autofill design" calls `autofillDesign` with the same
hardcoded table to fill in those tagged elements.
For API reference docs and instructions on running this example, see:
<https://www.canva.dev/docs/apps/examples/autofill/>.
Related examples: see `intents/request_data_table` for fetching a data table
from a Data Connector instead of a hardcoded one, and `design_interaction/brand_templates`
for selecting and applying a brand template before autofilling it.
NOTE: This example differs from what is expected for public apps to pass a Canva review:
- **Preview/beta API**: `autofillDesign` and `getDesignMetadata` are `@beta`
APIs, and `requestDataFieldMatching` isn't yet available in this example's
pinned `@canva/design` version — it will ship in a subsequent beta release.
Check the latest API status in the docs before relying on these in a
production app.
- **Static/hardcoded data**: The data used for matching and autofilling is a
fixed, hardcoded table. Production apps should use real data, typically
fetched from a Data Connector (see `intents/request_data_table`) or an
external API.
- **Localization**: Text content is hardcoded in English. Production apps
require proper internationalization using the `@canva/app-i18n-kit` package
for multi-language support.
- **Error handling**: Error handling is simplified for demonstration.
Production apps must implement comprehensive error handling with clear user
feedback and graceful failure modes.
MARKDOWN

API reference

Need help?