> ## Documentation Index > Fetch the complete documentation index at: https://www.canva.dev/docs/apps/llms.txt > Use this file to discover all available pages before exploring further. # 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: 1. If you haven't already, create a new app in the [Developer Portal](https://www.canva.com/developers/apps). For more information, refer to our [Quickstart guide](https://www.canva.dev/docs/apps/quickstart/). 2. In your app's configuration on the [Developer Portal](https://www.canva.com/developers/apps), ensure the "Development URL" is set to `http://localhost:8080`. 3. Clone the starter kit: ```shell git clone https://github.com/canva-sdks/canva-apps-sdk-starter-kit.git cd canva-apps-sdk-starter-kit ``` 4. Install dependencies: ```shell npm install ``` 5. Run the example: ```shell npm run start:example brand_templates ``` 6. Click the **Preview URL** link shown in the terminal to open the example in the Canva editor. ## Example app source code ```typescript // 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 authentication import { 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({ status: "idle" }); const [error, setError] = useState(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 => { 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 (
Brand templates Pick a brand template, preview its dataset fields, then apply it to the current design. {error && {error}} {(state.status === "preview" || state.status === "applying") && ( )}
); } function TemplatePreview({ metadata, data, isApplying, onApply, }: { metadata: BrandTemplateMetadata; data: BrandTemplateData; isApplying: boolean; onApply: () => void; }) { const dataset = metadata.dataset ?? []; return ( {metadata.keywords.length > 0 && ( Keywords: {metadata.keywords.join(", ")} )} {dataset.length === 0 ? ( This template has no dataset fields. ) : ( Dataset fields: {dataset.map((field) => ( {`• ${field.label} (${field.type})`} ))} )} Applied {data.applyCount} time{data.applyCount === 1 ? "" : "s"} by this brand, as tracked by the backend using the decoded brand template ID. ); } ```
```typescript // For usage information, see the README.md file. // Database type definitions for storing data per brand and brand template type BrandId = string; type BrandData = { name: string; brandTemplates: Map; }; 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(); }; let brandCounter = 1; export const createBrand = () => ({ name: `FooBar's Brand ${brandCounter++}`, brandTemplates: new Map(), }); ``` ```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( , ); } 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); } ``` ```markdown # Brand templates Demonstrates 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 ```
## API reference * [App UI Kit](https://www.canva.dev/docs/apps/app-ui-kit/) * [`applyTemplate`](https://www.canva.dev/docs/apps/api/preview/design-apply-template/) * [`auth.getCanvaUserToken`](https://www.canva.dev/docs/apps/api/latest/user-auth-get-canva-user-token/) * [`brandTemplate.verifyToken`](https://www.canva.dev/docs/apps/api/preview/app-middleware-express-brand-template-verify-token/) * [`getBrandTemplateMetadata`](https://www.canva.dev/docs/apps/api/preview/design-get-brand-template-metadata/) * [`prepareDesignEditor`](https://www.canva.dev/docs/apps/api/latest/intents-design-prepare-design-editor/) * [`requestBrandTemplates`](https://www.canva.dev/docs/apps/api/preview/design-request-brand-templates/) * [`tokenExtractors.fromQuery`](https://www.canva.dev/docs/apps/api/preview/app-middleware-express-token-extractors/) * [`user.verifyToken`](https://www.canva.dev/docs/apps/api/preview/app-middleware-express-user-verify-token/) ## Need help? * Join our [Community Forum](https://community.canva.dev/) * Report issues with this example on [GitHub](https://github.com/canva-sdks/canva-apps-sdk-starter-kit/issues)