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";import { auth } from "@canva/user";import { getDesignMetadata, getDesignToken } from "@canva/design";type 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>();const getDesignData = async () => {setState("loading");setError(undefined);try {const [designToken, authToken] = await Promise.all([getDesignToken(),auth.getCanvaUserToken(),]);const response = await fetch(`${BACKEND_HOST}/design/${designToken.token}`,{headers: {Authorization: `Bearer ${authToken}`,},},);return response.json();} catch {setError("Failed to get design data from server.");} finally {setState("idle");}};const saveDesignData = async () => {setState("loading");setError(undefined);try {const [designToken, authToken, { defaultPageDimensions: dimensions }] =await Promise.all([getDesignToken(),auth.getCanvaUserToken(),getDesignMetadata(),]);await fetch(`${BACKEND_HOST}/design/${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><MultilineInput autoGrow readOnly value={JSON.stringify(designData)} /><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
type 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
import * as jwt from "jsonwebtoken";import { JwksClient, SigningKeyNotFoundError } from "jwks-rsa";const CACHE_EXPIRY_MS = 60 * 60 * 1_000; // 60 minutesconst TIMEOUT_MS = 30 * 1_000; // 30 secondsconst CANVA_BASE_URI = "https://api.canva.com";/*** The JWT payload we'll decode contains:* designId - The ID of the Canva Design where this token was issued.* aud (audience) - We use the App ID to identify the targeted audience for this payload.* exp (expiry) - The expiry timestamp for this JWT, in seconds.* iat (issuedAt) - The timestamp at which this JWT was issued, in seconds.* nbf (notBefore) - The JWT should only be valid after this timestamp, in seconds.** See the JWT specification for more details on each claim and what it represents.* https://datatracker.ietf.org/doc/html/rfc7519#section-4.1*/type DesignToken = Omit<jwt.Jwt, "payload"> & {payload: {designId: string;aud: string;exp: number;iat: number;nbf: number;};};/*** A helper function for decoding the JWT and verifying that it originated from Canva by checking its signature.* @param appId - The ID of the app* @param designToken - The DesignToken JWT that contains the Design ID, App ID and User ID.* @returns A Promise that resolves to the payload contained by the DesignToken.*/export const decodeAndVerifyDesignToken = async (appId: string,designToken: string,) => {const unverifiedDecodedToken = jwt.decode(designToken, {complete: true,});if (unverifiedDecodedToken?.header?.kid == null) {throw new SigningKeyNotFoundError("Error verifying DesignToken: expected token to contain 'kid' claim header in order to produce a signing key.",);}const jwksClient = new JwksClient({cache: true,cacheMaxAge: CACHE_EXPIRY_MS,timeout: TIMEOUT_MS,rateLimit: true,jwksUri: `${CANVA_BASE_URI}/rest/v1/apps/${appId}/jwks`,});const key = await jwksClient.getSigningKey(unverifiedDecodedToken.header.kid);const publicKey = key.getPublicKey();const { payload } = jwt.verify(designToken, publicKey, {audience: appId,complete: true,}) as DesignToken;if (payload.designId == null || payload.aud == null) {throw new jwt.JsonWebTokenError("Invalid JWT payload");}/*** Convert current timestamp to seconds, as determined by the NumericDate object in the JWT specifications* See https://datatracker.ietf.org/doc/html/rfc7519#section-2*/const now = convertMillisecondsToSeconds(Date.now());/*** Dates provided in a JWT payload are in seconds, as per the NumericDate object in the JWT specification.* See https://datatracker.ietf.org/doc/html/rfc7519#section-2* We convert them to milliseconds before creating JS Date objects.*/if (payload.exp < now) {throw new jwt.TokenExpiredError("The provided DesignToken has expired.",new Date(convertSecondsToMilliseconds(payload.exp)),);}if (payload.iat > now) {throw new jwt.NotBeforeError("Invalid issue date for DesignToken",new Date(convertSecondsToMilliseconds(payload.iat)),);}if (payload.nbf > now) {throw new jwt.NotBeforeError("Cannot verify DesignToken prior to the NotBefore date",new Date(convertSecondsToMilliseconds(payload.nbf)),);}return payload;};const convertSecondsToMilliseconds = (seconds: number) => seconds * 1000;const convertMillisecondsToSeconds = (milliseconds: number) =>milliseconds / 1000;
TYPESCRIPT
import * as cors from "cors";import "dotenv/config";import * as express from "express";import { createBaseServer } from "../../../../utils/backend/base_backend/create";import { createJwtMiddleware } from "../../../../utils/backend/jwt_middleware";import { createBrand, createInMemoryDatabase, createUser } from "./database";import { decodeAndVerifyDesignToken } from "./decode_jwt";import { SigningKeyNotFoundError } from "jwks-rsa";import * as jwt from "jsonwebtoken";/*** TODO: add your CANVA_APP_ID to the .env file at the root level*/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();/*** Instantiate JWT middleware to be used to parse the auth token from the header.*/const jwtMiddleware = createJwtMiddleware(APP_ID);/*** TODO: Replace this with a real database.*/const data = createInMemoryDatabase();/*** TODO: 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());/*** TODO: Add this middleware to all routes that will receive authenticated requests from* your app.*/router.use(jwtMiddleware);/*** Endpoint for retrieving the data associated with a particular design.* Design data is stored per-user. Users are also separated into brands.*/router.get("/design/:token", async (req, res) => {/*** Ensure to retrieve Design ID by decoding a Canva-generated DesignToken JWT. We can trust the content of these* tokens and safely assume that the current user has access.** We strongly recommend you do not expose endpoints that receive plain Design IDs. Doing so will mean that anyone* could pass through an arbitrary Design ID, including the IDs of designs they don't actually have access to* within Canva.*/let designId: string;try {({ designId } = await decodeAndVerifyDesignToken(APP_ID, req.params.token));} catch (e) {return res.status(401).json({ error: "unauthorized", message: getErrorMessage(e) });}const { userId, brandId } = req.canva;const brand = data.get(brandId);const user = brand?.users?.get(userId);res.send(user?.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.*/router.post("/design/:token", async (req, res) => {/*** Ensure to retrieve Design ID by decoding a Canva-generated DesignToken JWT. We can trust the content of these* tokens and safely assume that the current user has access.** We strongly recommend you do not expose endpoints that receive plain Design IDs. Doing so will mean that anyone* could pass through an arbitrary Design ID, including the IDs of designs they don't actually have access to* within Canva.*/let designId: string;try {({ designId } = await decodeAndVerifyDesignToken(APP_ID, req.params.token));} catch (e) {return res.status(401).json({ error: "unauthorized", message: getErrorMessage(e) });}const { userId, brandId } = req.canva;let brand = data.get(brandId);if (brand == null) {brand = createBrand();data.set(brandId, brand);}let user = brand.users.get(userId);if (user == null) {user = createUser();brand.users.set(userId, user);}user.designs.set(designId, req.body);res.sendStatus(200);});const server = createBaseServer(router);server.start(process.env.CANVA_BACKEND_PORT);/*** Gets a readable error message to send back to the caller.* @param e - The Error object from which we derive the error message.*/const getErrorMessage = (e: unknown) => {if (e instanceof SigningKeyNotFoundError) {return "Public key not found";}if (e instanceof jwt.JsonWebTokenError) {return "Invalid token";}if (e instanceof jwt.TokenExpiredError) {return "Token expired";}return "An error has occurred while decoding the token.";};
TYPESCRIPT
import { AppUiProvider } from "@canva/app-ui-kit";import { createRoot } from "react-dom/client";import { App } from "./app";import "@canva/app-ui-kit/styles.css";const root = createRoot(document.getElementById("root") as Element);function render() {root.render(<AppUiProvider><App /></AppUiProvider>,);}render();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:- Token usage patterns are simplified for demonstration purposes only. Production apps should implement proper token refresh mechanisms, secure backend communication, and appropriate rate limiting for API calls- Error handling is simplified for demonstration. Production apps must implement comprehensive error handling with clear user feedback and graceful failure modes- Internationalization is not implemented. Production apps must support multiple languages using the `@canva/app-i18n-kit` package to pass Canva review requirements
MARKDOWN
API Reference
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)