Examples
App elements
Assets and media
Fundamentals
Intents
Design interaction
Drag and drop
Design elements
Localization
Content replacement
Design token
Secure token-based authentication and design access.
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 design_tokenSHELL -
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 {Button,FormField,MultilineInput,Rows,Text,TextInput,} from "@canva/app-ui-kit";import { useState, useEffect } from "react";import * as styles from "styles/components.css";// Canva SDKs for accessing user authentication and design metadataimport { auth } from "@canva/user";import { getDesignMetadata, getDesignToken } from "@canva/design";// Type definition for design data stored in the backendtype DesignData = {title: string;defaultDimensions: {width: number;height: number;};};export const App = () => {const [title, setTitle] = useState("");const [state, setState] = useState<"loading" | "idle">("idle");const [designData, setDesignData] = useState<DesignData | undefined>();const [error, setError] = useState<string | undefined>();// Retrieves design data from the backend using design tokens for secure accessconst getDesignData = async () => {setState("loading");setError(undefined);try {// Get both design token and user authentication token from Canva// Design tokens provide secure access to design-specific data// User tokens authenticate the current userconst [designToken, authToken] = await Promise.all([getDesignToken(),auth.getCanvaUserToken(),]);const response = await fetch(`${BACKEND_HOST}/design?designToken=${designToken.token}`,{headers: {Authorization: `Bearer ${authToken}`,},},);return response.json();} catch {setError("Failed to get design data from server.");} finally {setState("idle");}};// Saves design data to the backend using tokens and metadata from Canvaconst saveDesignData = async () => {setState("loading");setError(undefined);try {// Collect design token, user token, and current design metadata// getDesignMetadata() provides access to design properties like dimensionsconst [designToken, authToken, { defaultPageDimensions: dimensions }] =await Promise.all([getDesignToken(),auth.getCanvaUserToken(),getDesignMetadata(),]);await fetch(`${BACKEND_HOST}/design?designToken=${designToken.token}`, {headers: {Authorization: `Bearer ${authToken}`,"Content-Type": "application/json",},method: "POST",body: JSON.stringify({designData: {title,dimensions,},}),});await refreshDesignData();} catch {setError("Failed to save design data to server.");} finally {setState("idle");}};const refreshDesignData = () => getDesignData().then(setDesignData);useEffect(() => {refreshDesignData();}, []);return (<div className={styles.scrollContainer}><Rows spacing="3u"><Text>This example demonstrates how apps can use design tokens to save andretrieve data on a per-design basis.</Text><Text>Data stored for this design:</Text><MultilineInputautoGrowreadOnlyvalue={JSON.stringify(designData, null, 2)}/><Buttonloading={state === "loading"}disabled={state === "loading"}variant="primary"onClick={refreshDesignData}>Refresh</Button><FormFieldcontrol={(props) => <TextInput {...props} onChange={setTitle} />}label="Title"/><Buttonloading={state === "loading"}disabled={state === "loading"}variant="primary"onClick={saveDesignData}>Save</Button>{error && <Text tone="critical">{error}</Text>}</Rows></div>);};
TYPESCRIPT
// For usage information, see the README.md file.// Database type definitions for storing design data per brand and usertype BrandId = string;type BrandData = {name: string;users: Map<UserId, UserData>;};type UserId = string;type UserData = {name: string;designs: Map<DesignId, DesignData>;};type DesignId = string;type DesignData = {title: string;defaultDimensions: {width: number;height: number;};};/*** Create a Map object that will act as an in-memory database for this example. This DB stores user data on a per-brand* basis. Each brand contains multiple users, and each user contains multiple designs.*/export const createInMemoryDatabase = () => {return new Map<BrandId, BrandData>();};let brandCounter = 1;export const createBrand = () => ({name: `FooBar's Brand ${brandCounter++}`,users: new Map<UserId, UserData>(),});let userCounter = 1;export const createUser = () => ({name: `Foo Bar ${userCounter++}`,designs: new Map<DesignId, DesignData>(),});
TYPESCRIPT
// For usage information, see the README.md file./* eslint-disable @typescript-eslint/no-non-null-assertion -- user is 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, design, tokenExtractors } from "@canva/app-middleware/express";import { createBrand, createInMemoryDatabase, createUser } 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 retrieving the data associated with a particular design.* Design data is stored per-user. Users are also separated into brands.** The design.verifyToken() middleware verifies the design token from the query parameter* and populates req.canva.design with { designId, appId }. This ensures we only accept* valid, Canva-generated design tokens and prevents unauthorized access to arbitrary design IDs.*/router.get("/design",design.verifyToken({appId: APP_ID,tokenExtractor: tokenExtractors.fromQuery("designToken"),}),async (req, res) => {const { designId } = req.canva.design!;const { userId, brandId } = req.canva.user!;const brand = data.get(brandId);const userRecord = brand?.users?.get(userId);return res.send(userRecord?.designs?.get(designId) || {});},);/*** Endpoint for saving the data associated with a particular design.* Design data is stored per-user. Users are also separated into brands.** The design.verifyToken() middleware verifies the design token from the query parameter* and populates req.canva.design with { designId, appId }. This ensures we only accept* valid, Canva-generated design tokens and prevents unauthorized access to arbitrary design IDs.*/router.post("/design",design.verifyToken({appId: APP_ID,tokenExtractor: tokenExtractors.fromQuery("designToken"),}),async (req, res) => {const { designId } = req.canva.design!;const { userId, brandId } = req.canva.user!;let brand = data.get(brandId);if (brand == null) {brand = createBrand();data.set(brandId, brand);}let userRecord = brand.users.get(userId);if (userRecord == null) {userRecord = createUser();brand.users.set(userId, userRecord);}userRecord.designs.set(designId, req.body);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
# Design tokenDemonstrates how to obtain design tokens for accessing design metadata through external APIs. Shows token generation, authentication integration, and design data retrieval patterns.For API reference docs and instructions on running this example, see: https://www.canva.dev/docs/apps/examples/design-token/.Related examples: See fundamentals/fetch for general API communication, or design_interaction/export for design export functionality.NOTE: This example differs from what is expected for public apps to pass a Canva review:- **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
auth.getCanvaUserTokendesign.verifyTokengetDesignMetadatagetDesignTokenprepareDesignEditortokenExtractors.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)