Design token

Secure token-based authentication and design access.

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 design_token
    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 {
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 metadata
import { auth } from "@canva/user";
import { getDesignMetadata, getDesignToken } from "@canva/design";
// Type definition for design data stored in the backend
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>();
// Retrieves design data from the backend using design tokens for secure access
const 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 user
const [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 Canva
const saveDesignData = async () => {
setState("loading");
setError(undefined);
try {
// Collect design token, user token, and current design metadata
// getDesignMetadata() provides access to design properties like dimensions
const [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 and
retrieve data on a per-design basis.
</Text>
<Text>Data stored for this design:</Text>
<MultilineInput
autoGrow
readOnly
value={JSON.stringify(designData, null, 2)}
/>
<Button
loading={state === "loading"}
disabled={state === "loading"}
variant="primary"
onClick={refreshDesignData}
>
Refresh
</Button>
<FormField
control={(props) => <TextInput {...props} onChange={setTitle} />}
label="Title"
/>
<Button
loading={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 user
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
// 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 token
Demonstrates 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

Need help?