Examples
App elements
Assets and media
Fundamentals
Design interaction
Intents
Drag and drop
Design elements
Localization
Content replacement
Brand templates
Choose a brand template, preview its details, and apply it to the current design using a securely verified brand template token.
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 brand_templatesSHELL -
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, Rows, Text, Title } from "@canva/app-ui-kit";import type { BrandTemplateId, BrandTemplateMetadata } from "@canva/design";import {applyTemplate,getBrandTemplateMetadata,requestBrandTemplates,} from "@canva/design";import { useState } from "react";import * as styles from "styles/components.css";// Canva SDK for accessing user authenticationimport { auth } from "@canva/user";// Data the backend returns for a brand template. brandTemplateId is decoded// and verified server-side from the token — see backend/server.ts — since// the raw token from requestBrandTemplates isn't itself a usable// BrandTemplateId. applyCount is tracked per-brand.type BrandTemplateData = {brandTemplateId: BrandTemplateId;applyCount: number;};type State =| { status: "idle" }| { status: "loading" }| {status: "preview";token: string;metadata: BrandTemplateMetadata;data: BrandTemplateData;}| {status: "applying";token: string;metadata: BrandTemplateMetadata;data: BrandTemplateData;};export function App() {const [state, setState] = useState<State>({ status: "idle" });const [error, setError] = useState<string | null>(null);// Sends the brand template token to the backend, where// brandTemplate.verifyToken() decodes and verifies it before returning the// real brandTemplateId, along with how many times this brand has applied// the template.const getBrandTemplateData = async (brandTemplateToken: string,): Promise<BrandTemplateData> => {const authToken = await auth.getCanvaUserToken();const response = await fetch(`${BACKEND_HOST}/brand-template?brandTemplateToken=${brandTemplateToken}`,{headers: {Authorization: `Bearer ${authToken}`,},},);return response.json();};const handleChooseTemplate = async () => {setError(null);setState({ status: "loading" });try {const response = await requestBrandTemplates();if (response.status === "aborted") {setState({ status: "idle" });return;}// The token is an opaque, signed JWT — not itself a usable// BrandTemplateId. It's sent to the backend, where// brandTemplate.verifyToken() confirms it's a genuine, unmodified// token issued by Canva for this app, and decodes it into the real ID,// which is what the client-side calls below need.const token = response.brandTemplates[0]?.token;if (!token) {setState({ status: "idle" });setError("No brand template was selected.");return;}const data = await getBrandTemplateData(token);const metadata = await getBrandTemplateMetadata(data.brandTemplateId);setState({ status: "preview", token, metadata, data });} catch (err) {setState({ status: "idle" });setError(err instanceof Error ? err.message : "Failed to load brand template.",);}};const handleApplyTemplate = async () => {if (state.status !== "preview") return;const { token, metadata, data } = state;setError(null);setState({ status: "applying", token, metadata, data });try {const result = await applyTemplate({template: { id: data.brandTemplateId },mode: "overwrite",});if (result.status === "aborted") {setState({ status: "preview", token, metadata, data });return;}// Record the successful apply against the brand template. The backend// re-verifies the token via brandTemplate.verifyToken() before// trusting the brand template ID, rather than accepting it as a plain// request parameter.const authToken = await auth.getCanvaUserToken();await fetch(`${BACKEND_HOST}/brand-template?brandTemplateToken=${token}`,{headers: {Authorization: `Bearer ${authToken}`,},method: "POST",},);const refreshedData = await getBrandTemplateData(token);setState({status: "preview",token,metadata,data: refreshedData,});} catch (err) {setState({ status: "preview", token, metadata, data });setError(err instanceof Error ? err.message : "Failed to apply template.",);}};return (<div className={styles.scrollContainer}><Rows spacing="2u"><Title>Brand templates</Title><Text>Pick a brand template, preview its dataset fields, then apply it tothe current design.</Text><Buttonvariant="primary"onClick={handleChooseTemplate}loading={state.status === "loading"}stretch>Choose brand template</Button>{error && <Alert tone="critical">{error}</Alert>}{(state.status === "preview" || state.status === "applying") && (<TemplatePreviewmetadata={state.metadata}data={state.data}isApplying={state.status === "applying"}onApply={handleApplyTemplate}/>)}</Rows></div>);}function TemplatePreview({metadata,data,isApplying,onApply,}: {metadata: BrandTemplateMetadata;data: BrandTemplateData;isApplying: boolean;onApply: () => void;}) {const dataset = metadata.dataset ?? [];return (<Rows spacing="1u">{metadata.keywords.length > 0 && (<Text tone="secondary" size="small">Keywords: {metadata.keywords.join(", ")}</Text>)}{dataset.length === 0 ? (<Text tone="secondary">This template has no dataset fields.</Text>) : (<Rows spacing="0.5u"><Text size="small" tone="secondary">Dataset fields:</Text>{dataset.map((field) => (<Text key={field.label} size="small">{`• ${field.label} (${field.type})`}</Text>))}</Rows>)}<Text tone="secondary" size="small">Applied {data.applyCount} time{data.applyCount === 1 ? "" : "s"} by thisbrand, as tracked by the backend using the decoded brand template ID.</Text><Button variant="primary" onClick={onApply} loading={isApplying} stretch>Apply template</Button></Rows>);}
TYPESCRIPT
// For usage information, see the README.md file.// Database type definitions for storing data per brand and brand templatetype BrandId = string;type BrandData = {name: string;brandTemplates: Map<BrandTemplateId, BrandTemplateData>;};type BrandTemplateId = string;type BrandTemplateData = {applyCount: number;};/*** Create a Map object that will act as an in-memory database for this example. This DB stores* data on a per-brand basis. Each brand contains multiple brand templates, keyed by brand* template ID.*/export const createInMemoryDatabase = () => {return new Map<BrandId, BrandData>();};let brandCounter = 1;export const createBrand = () => ({name: `FooBar's Brand ${brandCounter++}`,brandTemplates: new Map<BrandTemplateId, BrandTemplateData>(),});
TYPESCRIPT
// For usage information, see the README.md file./* eslint-disable @typescript-eslint/no-non-null-assertion -- user and brandTemplate are guaranteed by verifyToken middleware */import cors from "cors";import "dotenv/config";import express from "express";import { createBaseServer } from "../../../../utils/backend/base_backend/create";import {user,brandTemplate,tokenExtractors,} from "@canva/app-middleware/express";import { createBrand, createInMemoryDatabase } from "./database";/*** Retrieve the CANVA_APP_ID from environment variables.* Set this in your .env file at the root level of the project.*/const APP_ID = process.env.CANVA_APP_ID;if (!APP_ID) {throw new Error(`The CANVA_APP_ID environment variable is undefined. Set the variable in the project's .env file.`,);}/*** Initialize ExpressJS router.*/const router = express.Router();/*** In-memory database for demonstration purposes.* Production apps should use a persistent database solution.*/const data = createInMemoryDatabase();/*** IMPORTANT: You must configure your CORS Policy** Cross-Origin Resource Sharing* ([CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS)) is an* [HTTP](https://developer.mozilla.org/en-US/docs/Glossary/HTTP)-header based* mechanism that allows a server to indicate any* [origins](https://developer.mozilla.org/en-US/docs/Glossary/Origin)* (domain, scheme, or port) other than its own from which a browser should* permit loading resources.** A basic CORS configuration would include the origin of your app in the* following example:* const corsOptions = {* origin: 'https://app-abcdefg.canva-apps.com',* optionsSuccessStatus: 200* }** The origin of your app is https://app-${APP_ID}.canva-apps.com, and note* that the APP_ID should to be converted to lowercase.** https://www.npmjs.com/package/cors#configuring-cors** You may need to include multiple permissible origins, or dynamic origins* based on the environment in which the server is running. Further* information can be found* [here](https://www.npmjs.com/package/cors#configuring-cors-w-dynamic-origin).*/router.use(cors());/*** JWT middleware for authenticating requests from Canva apps.* This should be applied to all routes that require user authentication.*/router.use(user.verifyToken({ appId: APP_ID }));/*** Endpoint for decoding a brand template token and retrieving the data* associated with that brand template. Brand template data is stored* per-brand, since brand templates belong to a brand rather than an* individual user.** The brandTemplate.verifyToken() middleware verifies the brand template token from the* query parameter and populates req.canva.brandTemplate with { brandTemplateId, appId }.* This ensures we only accept valid, Canva-generated brand template tokens and prevents* unauthorized access to arbitrary brand template IDs.** The response includes the decoded brandTemplateId, since the token itself* isn't a usable BrandTemplateId — the client needs this real, verified ID to* call getBrandTemplateMetadata and applyTemplate from @canva/design.*/router.get("/brand-template",brandTemplate.verifyToken({appId: APP_ID,tokenExtractor: tokenExtractors.fromQuery("brandTemplateToken"),}),async (req, res) => {const { brandTemplateId } = req.canva.brandTemplate!;const { brandId } = req.canva.user!;const brand = data.get(brandId);const brandTemplateData = brand?.brandTemplates?.get(brandTemplateId) ?? {applyCount: 0,};return res.send({ brandTemplateId, ...brandTemplateData });},);/*** Endpoint for recording that a brand template was applied to a design.* Brand template data is stored per-brand, since brand templates belong to a brand* rather than an individual user.** The brandTemplate.verifyToken() middleware verifies the brand template token from the* query parameter and populates req.canva.brandTemplate with { brandTemplateId, appId }.* This ensures we only increment the apply count for valid, Canva-generated brand template* tokens, and prevents unauthorized access to arbitrary brand template IDs.*/router.post("/brand-template",brandTemplate.verifyToken({appId: APP_ID,tokenExtractor: tokenExtractors.fromQuery("brandTemplateToken"),}),async (req, res) => {const { brandTemplateId } = req.canva.brandTemplate!;const { brandId } = req.canva.user!;let brand = data.get(brandId);if (brand == null) {brand = createBrand();data.set(brandId, brand);}const brandTemplateData = brand.brandTemplates.get(brandTemplateId) ?? {applyCount: 0,};brandTemplateData.applyCount += 1;brand.brandTemplates.set(brandTemplateId, brandTemplateData);return res.sendStatus(200);},);const server = createBaseServer(router);server.start(process.env.CANVA_BACKEND_PORT);
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
# Brand templatesDemonstrates the brand template workflow: choose a brand template with `requestBrandTemplates`, then send its token to a backend, where `brandTemplate.verifyToken()` from `@canva/app-middleware` verifies the token and decodes it into the real brand template ID. That ID — not the raw token — is what's used to preview the template's dataset fields with `getBrandTemplateMetadata` and apply it to the current design with `applyTemplate`, both from `@canva/design`. The backend also uses the ID to track how many times the template has been applied.For API reference docs and instructions on running this example, see: https://www.canva.dev/docs/apps/examples/brand-templates/.This example requires a backend with a valid `CANVA_APP_ID` configured in the root `.env` file. See [Running an example's backend](../../../README.md#running-an-examples-backend) for setup instructions.Related examples: See design_interaction/design_token for the equivalent pattern of verifying a token on the backend, or design_interaction/design_template_metadata for retrieving metadata about the templates used to create a design (a different, more mature API than the brand template APIs used here).NOTE: This example differs from what is expected for public apps to pass a Canva review:- **Preview/beta API**: `requestBrandTemplates`, `getBrandTemplateMetadata`, `applyTemplate`, and the brand template token verifier from `@canva/app-middleware` are `@beta` APIs — not yet guaranteed stable, and not suitable for production apps without checking the latest API status in the docs- **In-memory database**: Uses a simple in-memory storage for demonstration. Production apps should use persistent database solutions like PostgreSQL, MongoDB, or similar- **CORS configuration**: Uses permissive CORS settings. Production apps must restrict CORS to only allow requests from your app's specific origin (https://app-{app-id}.canva-apps.com)- **Token management**: Token usage patterns are simplified for demonstration purposes. Production apps should implement proper token refresh mechanisms, secure backend communication, and appropriate rate limiting for API calls- **Error handling**: Error handling is simplified for demonstration. Production apps must implement comprehensive error handling with clear user feedback and graceful failure modes- **Internationalization**: Not implemented in this example. Production apps must support multiple languages using the `@canva/app-i18n-kit` package to pass Canva review requirements- **Code structure**: Production apps using [intents](https://www.canva.dev/docs/apps/intents/) are recommended to call the prepareDesignEditor function from src/intents/design_editor/index.tsx
MARKDOWN
API reference
- App UI Kit
applyTemplateauth.getCanvaUserTokenbrandTemplate.verifyTokengetBrandTemplateMetadataprepareDesignEditorrequestBrandTemplatestokenExtractors.fromQueryuser.verifyToken
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)