Appearance
Uploading Assets
Overview
How apps can upload, download, and interact with assets.
An asset is a file that exists in Canva's backend, such as an image, video, or audio file. Apps can upload, download, and otherwise interact with assets in a variety of ways.
Asset types
Apps can interact with the following types of assets:
- Audio
- Fonts
- Images
- Videos
The type of asset affects how an app can interact with it. For example, apps can upload and download image assets, but can only use font assets when creating text — they can't upload or download font assets.
The following table indicates how apps can interact with each type of asset:
| Upload | Download | Use | |
|---|---|---|---|
| Audio | |||
| Fonts | |||
| Images | |||
| Videos |
Asset ownership
There are two kinds of assets:
- Public assets
- Private assets
There are some differences in how apps can interact with each kind of asset.
Public assets
A public asset is one that's owned by Canva — that is, it was uploaded by Canva and is available to most users (with some restrictions, depending on how the asset was licensed). These assets appear in the Elements tab.
In certain contexts, apps can download public assets, but they can never upload them.
Private assets
A private asset is one that was uploaded by a user and exists within their media library. The user owns the asset and no one else has access to it unless they choose to share it. These assets appear in the Uploads tab.
In certain contexts, apps can upload and/or download private assets.
Asset references
All assets have an asset reference, also known as a ref.
A ref is a unique identifier that points to an asset in Canva's backend.
Refs are used throughout the Apps SDK in a variety of contexts. For example, when an asset is uploaded, a ref for that asset is returned and can be used to then interact with that asset:
tsx
import { type ImageRef, upload } from "@canva/asset";
// If you are uploading an image that is derived from another image, pass its imageRef, otherwise set it as undefined
const parentRef = "PLACEHOLDER_IMAGE_REF" as ImageRef;
// Upload an image asset
const asset = await upload({
type: "image",
parentRef,
mimeType: "image/jpeg",
url: "https://example.com/image.jpg",
thumbnailUrl: "https://example.com/thumbnail.jpg",
aiDisclosure: "none",
});
// Use the ref of the asset
console.log(asset.ref);Similarly, refs can sometimes be retrieved for existing assets, such as when selecting a font from a font picker.
The various ways that refs can be used are explained throughout the documentation.
Important: Refs are opaque strings
Refs are opaque strings and apps shouldn't rely on them remaining the same. In practice, this means that apps should only use refs soon after they're created or retrieved and should not store them for later use.
Apps can upload private assets to a user's media library. When the upload succeeds or fails, a toast notification is shown to the user. (The notification itself is not customizable.)
<Tabs> <Tab name="Audio"> ```tsx import { upload } from "@canva/asset";
await upload({
type: "audio",
title: "Example audio",
mimeType: "audio/mp3",
durationMs: 86047,
url: "https://example.com/audio.mp3",
aiDisclosure: "none",
});
```
</Tab>
<Tab name="Images"> ```tsx import { type ImageRef, upload } from "@canva/asset";
// If you are uploading an image that is derived from another image, pass its imageRef, otherwise set it as undefined
const parentRef = "PLACEHOLDER_IMAGE_REF" as ImageRef;
await upload({
type: "image",
parentRef,
mimeType: "image/jpeg",
url: "https://example.com/image.jpg",
thumbnailUrl: "https://example.com/thumbnail.jpg",
aiDisclosure: "none",
});
```
</Tab>
<Tab name="Videos"> ```tsx import { type VideoRef, upload } from "@canva/asset";
// If you are uploading a video that is derived from another video, pass its videoRef, otherwise set it as undefined
const parentRef = "PLACEHOLDER_VIDEO_REF" as VideoRef;
await upload({
type: "video",
parentRef,
mimeType: "video/mp4",
url: "https://example.com/video.mp4",
thumbnailImageUrl: "https://example.com/thumbnail-image.jpg",
thumbnailVideoUrl: "https://example.com/thumbnail-video.mp4",
aiDisclosure: "none",
});
```
</Tab> </Tabs>
Note: All assets must comply with Canva's Acceptable Use Policy.
Supported file formats
When uploading assets, the following file formats are supported:
<Tabs> <Tab name="Audio"> | Name | MIME type | Common file extensions | | --------- | --------------- | ----------------------------- | | MPEG | "audio/mpeg" | .mpg, .mpeg, .mp1, .mp2, .mp3 | | MP4 Audio | "audio/mp4" | .mp4, .m4a | | M4A | "audio/x-m4a" | .m4a | | MP3 | "audio/mp3" | .mp3 | | OGG | "audio/ogg" | .ogg, .oga | | WAV | "audio/wav" | .wav | | X-WAV | "audio/x-wav" | .wav | | WEBM | "audio/webm" | .webm | </Tab>
<Tab name="Images"> | Name | MIME type | Common file extensions | | ---- | ----------------- | ---------------------- | | HEIC | "image/heic" | .heic | | JPEG | "image/jpeg" | .jpg, .jpeg | | PNG | "image/png" | .png | | SVG | "image/svg+xml" | .svg | | TIFF | "image/tiff" | .tiff, .tif | | WebP | "image/webp" | .webp |
**Note:** In the Apps SDK, GIFs are considered to be videos, not images.
</Tab>
<Tab name="Videos"> | Name | MIME types | Common file extensions | | --------- | ---------------------------------- | ---------------------- | | AVI | "video/avi", "video/x-msvideo" | .avi | | GIF | "image/gif" | .gif | | M4V | "video/x-m4v" | .m4v | | Matroska | "video/x-matroska" | .mkv | | QuickTime | "video/quicktime" | .mov | | MP4 | "video/mp4" | .mp4 | | MPEG | "video/mpeg" | .mpg, .mpeg | | WebM | "video/webm" | .webm |
**Note:** In video files, transparent or translucent pixels are converted to black pixels.
</Tab> </Tabs>
File size limits
When uploading assets, the following file size limits apply:
| Asset type | Maximum file size |
|---|---|
| Audio | 250 MB |
| Images (Raster) | 50 MB |
| Images (SVG) | 3 MB |
| Videos | 1000 MB |
Asset URLs
When uploading an asset, the asset is downloaded via Canva's backend. For this reason, asset URLs must be available via the public internet — that is, they can't be uploaded from localhost URLs.
Thumbnail URLs
When uploading image and video assets, a thumbnail must be provided. In the case of videos, a preview video can also be provided. These thumbnails and previews are shown to the user:
- in the Uploads tab
- in the editor, while the asset is uploading
For security reasons, the URLs of these thumbnails and previews must support Cross-Origin Resource Sharing (CORS). If CORS is not supported, the URLs won't load and an error will appear in the JavaScript Console.
AI disclosure
When uploading assets, apps must disclose if the asset was created or significantly altered with AI. This helps users make informed decisions about the content they interact with.
These are some examples of when AI usage must be disclosed:
- Generating a new image from scratch
- Changing the style of an image
- Removing an object and replacing it with new content
- Changing facial expressions of people
- Compositing multiple images together
- Expanding images by generating content at the edges
- Replacing backgrounds with generated content
These are some examples of when AI usage does not require disclosure:
- Using existing assets from an asset library
- Correcting red eyes
- Removing backgrounds without replacement
- Changing object colors
- Detecting image defects and suggesting fixes
- Adjusting brightness and contrast
- Upscaling images
If the app used AI to create or alter an asset, set aiDisclosure to "app_generated":
tsx
import { type ImageRef, upload } from "@canva/asset";
// If you are uploading an image that is derived from another image, pass its imageRef, otherwise set it as undefined
const parentRef = "PLACEHOLDER_IMAGE_REF" as ImageRef;
const asset = await upload({
type: "image",
parentRef,
mimeType: "image/jpeg",
url: "https://example.com/ai-generated-image.jpg",
thumbnailUrl: "https://example.com/ai-generated-thumbnail.jpg",
aiDisclosure: "app_generated",
});Otherwise, set aiDisclosure to "none":
tsx
import { type ImageRef, upload } from "@canva/asset";
// If you are uploading an image that is derived from another image, pass its imageRef, otherwise set it as undefined
const parentRef = "PLACEHOLDER_IMAGE_REF" as ImageRef;
const asset = await upload({
type: "image",
parentRef,
mimeType: "image/jpeg",
url: "https://example.com/ai-generated-image.jpg",
thumbnailUrl: "https://example.com/ai-generated-thumbnail.jpg",
aiDisclosure: "none",
});Checking upload progress
Apps can detect when an asset has finished uploading. An example of how an app might use this behavior is to render a loading state while the upload is in progress.
<Tabs> <Tab name="Audio"> ```tsx import { upload } from "@canva/asset"; import { addAudioTrack } from "@canva/design";
// Upload an audio asset
const asset = await upload({
type: "audio",
title: "Example audio",
mimeType: "audio/mp3",
durationMs: 86047,
url: "https://example.com/audio.mp3",
aiDisclosure: "none",
});
// Wait for the upload to complete
await asset.whenUploaded();
```
</Tab>
<Tab name="Images"> ```tsx import { type ImageRef, upload } from "@canva/asset";
// If you are uploading an image that is derived from another image, pass its imageRef, otherwise set it as undefined
const parentRef = "PLACEHOLDER_IMAGE_REF" as ImageRef;
// Upload an image asset
const asset = await upload({
type: "image",
parentRef,
mimeType: "image/jpeg",
url: "https://example.com/image.jpg",
thumbnailUrl: "https://example.com/thumbnail.jpg",
aiDisclosure: "none",
});
// Wait for the upload to complete
await asset.whenUploaded();
```
</Tab>
<Tab name="Videos"> ```tsx import { type VideoRef, upload } from "@canva/asset";
// If you are uploading a video that is derived from another video, pass its videoRef, otherwise set it as undefined
const parentRef = "PLACEHOLDER_VIDEO_REF" as VideoRef;
// Upload a video asset
const asset = await upload({
type: "video",
parentRef,
mimeType: "video/mp4",
url: "https://example.com/video.mp4",
thumbnailImageUrl: "https://example.com/thumbnail-image.jpg",
thumbnailVideoUrl: "https://example.com/thumbnail-video.mp4",
aiDisclosure: "none",
});
// Wait for the upload to complete
await asset.whenUploaded();
```
</Tab> </Tabs>
What's possibly surprising though is that apps don't have to wait for the upload to complete before the asset reference is available. Apps can use the asset reference as soon as the upload starts.
<Tabs> <Tab name="Audio"> ```tsx import { upload } from "@canva/asset"; import { addAudioTrack } from "@canva/design";
// Upload an audio asset
const asset = await upload({
type: "audio",
title: "Example audio",
mimeType: "audio/mp3",
durationMs: 86047,
url: "https://example.com/audio.mp3",
aiDisclosure: "none",
});
// You can use the asset reference immediately
console.log(asset.ref);
// Use the asset reference to add an audio track
await addAudioTrack({
ref: asset.ref,
});
```
</Tab>
<Tab name="Images"> ```tsx import { type ImageRef, upload } from "@canva/asset"; import { addElementAtPoint } from "@canva/design";
// If you are uploading an image that is derived from another image, pass its imageRef, otherwise set it as undefined
const parentRef = "PLACEHOLDER_IMAGE_REF" as ImageRef;
// Upload an image asset
const asset = await upload({
type: "image",
parentRef,
mimeType: "image/jpeg",
url: "https://example.com/image.jpg",
thumbnailUrl: "https://example.com/thumbnail.jpg",
aiDisclosure: "none",
});
// You can use the asset reference immediately
console.log(asset.ref);
// Use the asset reference to add an image element
await addElementAtPoint({
type: "image",
ref: asset.ref,
});
```
</Tab>
<Tab name="Videos"> ```tsx import { type VideoRef, upload } from "@canva/asset"; import { addElementAtPoint } from "@canva/design";
// If you are uploading a video that is derived from another video, pass its videoRef, otherwise set it as undefined
const parentRef = "PLACEHOLDER_VIDEO_REF" as VideoRef;
// Upload a video asset
const asset = await upload({
type: "video",
parentRef,
mimeType: "video/mp4",
url: "https://example.com/video.mp4",
thumbnailImageUrl: "https://example.com/thumbnail-image.jpg",
thumbnailVideoUrl: "https://example.com/thumbnail-video.mp4",
aiDisclosure: "none",
});
// You can use the asset reference immediately
console.log(asset.ref);
// Use the asset reference to add a video element
await addElementAtPoint({
type: "video",
ref: asset.ref,
});
```
</Tab> </Tabs>
Handling errors
A range of errors may occur when uploading assets. For example, if the user doesn't have enough storage space in their media library, a "quota_exceeded" error will be thrown:
ts
import { type ImageRef, upload } from "@canva/asset";
import { CanvaError } from "@canva/error";
// If you are uploading an image that is derived from another image, pass its imageRef, otherwise set it as undefined
const parentRef = "PLACEHOLDER_IMAGE_REF" as ImageRef;
try {
const asset = await upload({
type: "image",
parentRef,
mimeType: "image/jpeg",
url: "https://example.com/image.jpg",
thumbnailUrl: "https://example.com/thumbnail.jpg",
aiDisclosure: "none",
});
await asset.whenUploaded();
} catch (error) {
if (error instanceof CanvaError) {
if (error.code === "quota_exceeded") {
console.error("The user is out of storage space.");
} else {
console.error("An error occurred:", error.code);
}
}
}Some errors propagate from whenUploaded, so we recommend wrapping both methods in a try/catch block.
To learn more about handling errors in general, see Handling errors.
Downloading assets
In some contexts, apps can download assets. For example, when a user selects image or video content, an app can detect the selection of that content and download the underlying media.
Getting asset URLs
Apps can use an asset's ref to generate a temporary URL for an asset. The app can then use this URL to download the underlying media. For security reasons, asset URLs expire after a short period of time.
Apps should use asset URLs as soon as possible, rather than storing them for later use.
<Tabs> <Tab name="Images"> ```tsx import { getTemporaryUrl } from "@canva/asset";
const imageRef = "..." as any; // Placeholder for the ref of the image
const { url } = await getTemporaryUrl({
type: "image",
ref: imageRef,
});
console.log(url);
```
</Tab>
<Tab name="Videos"> ```tsx import { getTemporaryUrl } from "@canva/asset";
const videoRef = "..." as any; // Placeholder for the ref of the video
const { url } = await getTemporaryUrl({
type: "video",
ref: videoRef,
});
console.log(url);
```
</Tab> </Tabs>
Downloading assets via the frontend
The simplest way to download assets is via the frontend. The main downside of this approach is that it's impractical to process large files via the frontend, such as videos. It also consumes more of the user's bandwidth.
<Tabs> <Tab name="Images"> ```tsx import { getTemporaryUrl } from "@canva/asset";
const imageRef = "..." as any; // Placeholder for the ref of the image
const { url } = await getTemporaryUrl({
type: "image",
ref: imageRef,
});
// Download the image using the Fetch API
const response = await fetch(url);
const blob = await response.blob();
```
</Tab>
<Tab name="Videos"> ```tsx import { getTemporaryUrl } from "@canva/asset";
const videoRef = "..." as any; // Placeholder for the ref of the video
const { url } = await getTemporaryUrl({
type: "video",
ref: videoRef,
});
// Download the video using the Fetch API
const response = await fetch(url);
const blob = await response.blob();
```
</Tab> </Tabs>
Downloading assets via the backend
The most versatile option is to pass the URL of the asset to the app's backend and download it there. This allows the app to process larger files in more complex ways, but it does require more code and infrastructure.
<Tabs> <Tab name="Images"> ```tsx import { getTemporaryUrl } from "@canva/asset";
const imageRef = "..." as any; // Placeholder for the ref of the image
// Get a temporary URL for the image
const { url } = await getTemporaryUrl({
type: "image",
ref: imageRef,
});
// Send the URL to your backend
await fetch("https://your-backend.com/process-image", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ imageUrl: url }),
});
```
</Tab>
<Tab name="Videos"> ```tsx import { getTemporaryUrl } from "@canva/asset";
// Get a temporary URL for the video
const { url } = await getTemporaryUrl({
type: "video",
ref: videoRef,
});
// Send the URL to your backend
await fetch("https://your-backend.com/process-video", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ videoUrl: url }),
});
```
</Tab> </Tabs>
Deriving assets
A common pattern for apps is to download an asset, modify it in some way, and then upload a new asset. For example, an app might download and apply an effect to an image.
When a new asset is created from an existing asset, it's known as a derived asset, and when an app uploads a derived asset, it must provide the ref of the asset it was derived from. We verify this during the app review process.
The following code sample demonstrates the basic flow of deriving an asset:
tsx
import { upload, getTemporaryUrl } from "@canva/asset";
const originalImageRef = "..." as any; // Placeholder for the ref of the original image
// Get the original image
const { url } = await getTemporaryUrl({
type: "image",
ref: originalImageRef,
});
// Process the image in some way
const response = await fetch("https://your-backend.com/process-image", {
method: "POST",
body: JSON.stringify({ imageUrl: url }),
});
const { processedImageUrl } = await response.json();
// Upload the processed image as a derived asset
const derivedAsset = await upload({
type: "image",
mimeType: "image/jpeg",
url: processedImageUrl,
thumbnailUrl: processedImageUrl,
aiDisclosure: "none",
parentRef: originalImageRef,
});This is required because Canva licenses assets from a number of creators and it's only by providing the ref of existing assets that Canva can ensure these licensing requirements are met.
A side-effect of this requirement is that apps are not allowed to combine multiple assets into a single asset, as the mechanism we use to track asset licensing only supports individual derived assets.
API reference
upload
Using Color Selectors
How to prompt users to select a color.
Sometimes, apps want to prompt users to select a color. For example, an app for creating patterns might want to prompt users to select the colors for a pattern. To ensure a consistent user experience, apps can use the Apps SDK to open a built-in color selector flyout and detect the selection of colors.
Features
- A familiar user experience that's deeply integrated with Canva.
- Integrated with Brand Kit, helping teams to remain on-brand.
- Canva Pro users automatically get access to Pro-only features.
The color selector feature works within different Canva design types, such as Presentations and Docs. See Feature support for more information.
The user experience
Apps are responsible for determining how a color selector opens, but a common pattern is to open the selector when a user clicks a Swatch component.
The color selector itself appears in a flyout interface that's "pinned" to the element that opened it. The exact position of the flyout is calculated and controlled by Canva.
Users can select document colors, Brand Kit colors, or default colors. If non-breaking features are added to the color selector in the future, they will be automatically available to apps without requiring code changes.
While the color selector is open, an invisible overlay is rendered on top of the app. This overlay blocks interactions with the app, such as clicking or scrolling, until the color selector is closed.
Color selection scopes
The color selector can be configured with one or more scopes. These scopes determine the user experience. At the moment, "solid" is the only supported scope. When enabled, this scope allows the user to select solid colors.
An example of a scope that will be supported in the future is the "palette" scope. This scope will allow users to select color palettes — that is, combinations of colors.
How to open a color selector
Step 1: Enable the required scopes
In the Developer Portal, enable the following scopes:
canva:brandkit:readcanva:design:content:read
In the future, the Apps SDK will throw an error if the required scopes aren't enabled.
To learn more, see Configuring scopes.
Step 2: Set up an anchor
An anchor is the element to which the color selector will be "pinned" when it's open. This could be a custom component, but we generally recommend using an App UI Kit component, such as the Swatch component:
tsx
import { Rows, Swatch } from "@canva/app-ui-kit";
import * as React from "react";
import * as styles from "styles/components.css";
export function App() {
return (
<div className={styles.scrollContainer}>
<Rows spacing="1u">
<Swatch
fill={["#ff0099"]}
onClick={() => {
// code goes here
}}
/>
</Rows>
</div>
);
}Step 3: Open the color selector
Import the
openColorSelectormethod from the@canva/assetpackage:tsximport { openColorSelector } from "@canva/asset";In the
onClickhandler, get the position and dimensions of the anchor:tsx<Swatch fill={["#ff0099"]} onClick={async (event) => { const anchor = event.currentTarget.getBoundingClientRect(); }} />Register a callback with the
openColorSelectormethod, passing in the position and dimensions of the anchor as the first argument, and setting the scope and callback as the second argument:tsx<Swatch fill={["#ff0099"]} onClick={async (event) => { const anchor = event.currentTarget.getBoundingClientRect(); await openColorSelector(anchor, { scopes: ["solid"], onColorSelect: (event) => { console.log(event.selection); }, }); }} />The callback receives an object as its only argument. This object contains information about the color selection event.
Step 4: Handle the color selection
Set up a state variable for tracking the currently selected color:
tsxconst [color, setColor] = React.useState<string>("#ff0099");Pass the color state into the
Swatchcomponent and, in theonClickhandler, update the state variable:tsx<Swatch fill={[color]} onClick={async (event) => { const anchor = event.currentTarget.getBoundingClientRect(); await openColorSelector(anchor, { scopes: ["solid"], onColorSelect: (event) => { if (event.selection.type === "solid") { setColor(event.selection.hexString); } }, }); }} />
(Optional) Step 5: Handle multiple colors
Sometimes, apps need to render multiple selectable colors, such as by rendering multiple Swatch components.
The problem is, when a user opens a color selector, the color that's displayed as selected defaults to the most recently selected color, even if the most recent color was selected via a different selector. This leads to the following bug:
- In Color Selector #1, the user selects a color.
- In Color Selector #2, the color from Color Selector #1 is display as selected.
To solve this problem, explicitly define the color to display as selected for each color selector:
tsx
<Swatch
fill={[color]}
onClick={async (event) => {
const anchor = event.currentTarget.getBoundingClientRect();
await openColorSelector(anchor, {
scopes: ["solid"],
selectedColor: color
? {
type: "solid",
hexString: color,
}
: undefined,
onColorSelect: (event) => {
console.log(event.selection);
},
});
}}
/>Here, we're passing the previously selected color into the openColorSelector method via the selectedColor property, or a value of undefined if there isn't a previously selected color.
The selectedColor property is not required if an app only renders one selectable color.
(Optional) Step 6: Close the color selector
The openColorSelector method returns a Promise that, once resolved, returns a function. This function closes the color selector flyout:
tsx
// Open the color selector
const closeColorSelector = await openColorSelector(anchor, {
scopes: ["solid"],
onColorSelect: (event) => {
if (event.selection.type === "solid") {
setColor(event.selection.hexString);
}
},
});
// Close the color selector
closeColorSelector();Most apps don't need to call this function. The color selector automatically closes when the user clicks outside of the flyout, and this is usually the best behavior. Even so, this function is sometimes a useful escape hatch.
To use this function, it often makes sense to store the function in a state variable so that it can be called throughout the lifecycle of the app. To store and then call the function from a state variable:
Import the
CloseColorSelectorFntype:tsximport { CloseColorSelectorFn, openColorSelector } from "@canva/asset";Create a state variable for storing the function:
tsxconst [closeColorSelector, setCloseColorSelector] = setState<CloseColorSelectorFn>();Store the function returned by the
openColorSelectormethod:tsxconst closingFn = await openColorSelector(anchor, { scopes: ["solid"], onColorSelect: (event) => { if (event.selection.type === "solid") { setColor(event.selection.hexString); } }, }); setCloseColorSelectorFn(() => closingFn);Warning: If you set a state variable to a function, React will assume the function is an initializer function. You can account for this behavior by passing in an arrow function. To learn more, see the official docs.
Elsewhere in the app, call the
closeColorSelectormethod:tsxcloseColorSelector?.();Here, we're using the
?symbol becausecloseColorSelectorwill beundefineduntil the color selector is opened for the first time.
API reference
openColorSelector
Code sample
tsx
import { Rows, Swatch } from "@canva/app-ui-kit";
import { openColorSelector } from "@canva/asset";
import * as React from "react";
import * as styles from "styles/components.css";
import { useFeatureSupport } from "@canva/app-hooks";
export function App() {
const [color, setColor] = React.useState<string>("#ff0099");
const isSupported = useFeatureSupport();
async function handleClick(event) {
if (isSupported(openColorSelector)) {
const anchor = event.currentTarget.getBoundingClientRect();
await openColorSelector(anchor, {
scopes: ["solid"],
onColorSelect: (event) => {
if (event.selection.type === "solid") {
setColor(event.selection.hexString);
}
},
});
}
}
return (
<div className={styles.scrollContainer}>
<Rows spacing="1u">
<Swatch
fill={[color]}
onClick={handleClick}
disabled={!isSupported(openColorSelector)}
/>
</Rows>
</div>
);
}