Design Editing API
We're actively developing the Design Editing API. Got a feature request or bug report? Let us know in our Developer Community(opens in a new tab or window)!
The Design Editing API enables apps to read and edit the "ingredients" of a design, such as pages and elements. This unlocks a range of powerful capabilities for programmatically creating and manipulating designs.
This page introduces the fundamentals of working with the openDesign API, along with some common use-cases. To understand the full scope of what's possible with the API, see the latest or preview versions of the openDesign API reference.
Our design guidelines help you create a high-quality app that easily passes app review.

Core concepts
The Design Editing API centers around a single openDesign method:
import { openDesign } from "@canva/design";
Understanding these core concepts is essential before using the API:
- Design contexts: Determines what part of the design to edit (
current_pagevs.all_pages) - Sessions: Provides access to design data and helper functions.
- Syncing: Controls when changes are applied to the live design.
- Conflicts: How Canva handles simultaneous edits.
- Lists: Special data structures for working with elements and other collections.
Design contexts
The first argument of the openDesign method specifies a design context:
import { openDesign } from "@canva/design";// Edit the current page onlyawait openDesign({ type: "current_page" }, async (session) => {console.log(session);});// Edit all pages in the designawait openDesign({ type: "all_pages" }, async (session) => {console.log(session);});
This determines what part of the design to read and edit:
-
current_page: Provides direct access to edit the current page. This is ideal for apps that focus on single-page operations or when you only need to work with the current page. -
all_pages: Provides access to all pages in the design using an iterative approach. This is ideal for apps that need to perform operations across multiple pages, such as bulk editing or design-wide transformations.The
all_pagesdesign context is currently in preview and only available in the preview package.
Sessions
The second argument of the openDesign method is a callback function that receives a session object. The contents of this object depends on the design context.
Sessions have a 1-minute expiry. The callback-based design of the API encourages you to complete your editing operations efficiently and avoid holding sessions for extended periods. Canva automatically handles session cleanup when the callback completes, so you don't need to manually dispose of session resources.
Current page sessions
When the design context is current_page, the session object contains:
page: A snapshot of the current page, including the elements it contains.helpers: Functions for working with elements within the page.sync: A method for syncing the snapshot with the live design.
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {console.log(session.page); // the snapshot of the current pageconsole.log(session.helpers); // helper functions (e.g., group, ungroup)console.log(session.sync); // the sync() method});
API reference for current_page sessions: openDesign
Multi-page sessions
The all_pages design context is currently in preview and only available in the preview package.
When the design context is all_pages, the session object contains:
pageRefs: A catalog of all pages in the design as references.helpers: Functions for working with pages.sync: A method for syncing changes with the live design.
import { openDesign } from "@canva/design";await openDesign({ type: "all_pages" }, async (session) => {console.log(session.pageRefs); // catalog of all pagesconsole.log(session.helpers); // helper functions (e.g., openPage)console.log(session.sync); // the sync() method});
API reference for all_pages sessions: openDesign
To edit individual pages within a multi-page session, use the openPage helper method, which provides a similar interface to current page sessions:
await openDesign({ type: "all_pages" }, async (session) => {for (const pageRef of session.pageRefs.toArray()) {await session.helpers.openPage(pageRef, async (pageResult) => {console.log(pageResult.page); // snapshot of this specific pageconsole.log(pageResult.helpers); // helper functions used within this page (e.g., group, ungroup)});}});
Each pageRef object contains essential information about a page:
type: The page type ("absolute"or"unsupported").locked: Whether the page is locked for editing.
Syncing
While the callback is running, the live design may continue to change. For example, someone collaborating on the design may change it in some way. This means that the snapshot of the design and live design can fall out of sync.
To account for this, the callback receives a sync method:
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute") {return;}// Get the initial count of elementsconst initialCount = session.page.elements.count();console.log(`Initial element count: ${initialCount}`);// The sync method updates the snapshot with the latest stateawait session.sync();// If someone else added elements, the count might be differentconst updatedCount = session.page.elements.count();console.log(`Updated element count: ${updatedCount}`);if (updatedCount !== initialCount) {console.log("The design was modified by another user!");}});
The sync method does two things:
- Updates the live design with any edits that have been made to the snapshot.
- Updates the snapshot, so that the callback has access to the latest state of the design.
That first point is particularly important, as edits to the snapshot are not automatically reflected in the design. Apps must call the sync method for the edits to be applied:
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute" || session.page.locked) {return;}// Create a text elementconst textElementState =session.helpers.elementStateBuilder.createTextElement({top: 100,left: 100,width: 200,text: {regions: [{text: "This text won't appear until we sync!",formatting: {fontSize: 20,color: "#ff0099",},},],},});// Add the element to the pagesession.page.elements.insertAfter(undefined, textElementState);// At this point, the element exists in the snapshot but *not* in the live designconsole.log("Element added to snapshot");// Call sync to apply the changes to the live designawait session.sync();console.log("Changes synced - element now visible in the design!");});
Any edits made to the snapshot that are not synced will be discarded when the callback is disposed.
Syncing best practices
For optimal performance and user experience, follow these syncing guidelines:
Batch your operations: Complete all your editing operations and sync once at the end, rather than syncing after each individual change. This approach is more efficient, provides better performance, and prevents rate limiting.
Consider the user experience: Each sync call creates a separate undo operation. Frequent syncing results in multiple undo steps, which can confuse users.
Sync mid-operation only when needed: You can call sync during your operations if you need to check for changes made by other users, but this should be done sparingly.
Conflicts
It's possible for there to be conflicts between the snapshot and the live design. In these cases, Canva will attempt to resolve the conflict. If the conflict can't be resolved, the edit that occurs outside of the app will take precedence.
Lists
The Design Editing API uses special "list" data structures that are similar to, but not identical to, JavaScript arrays. These provide optimized methods for working with design elements.
Lists are used throughout the API for collections like:
page.elements: Elements on a page.group.contents: Elements within a group.shape.paths: Paths within a shape.
List methods:
- Reading:
count,toArray,forEach,filter - Mutating:
insertBefore,insertAfter,moveBefore,moveAfter,delete
API reference for lists: List
The available methods depend on the thing being operated on. For example, when reading a shape element, the shape's paths are read-only, so mutation methods aren't available.
Don't assume array methods work on lists. If you need array functionality, you should use the provided list methods or call toArray() first.
Pages
Designs are made up of one or more pages.
Using the Design Editing API, apps can:
- Read the properties of pages (e.g., whether or not the page is locked)
- Read and edit the page's ingredients (e.g., the background of a page)
Page types
All pages have an associated type:
import { openDesign } from "@canva/design";// Current page context - only absolute pages are supportedawait openDesign({ type: "current_page" }, async (session) => {console.log(session.page.type); // => "absolute" or "unsupported"});// Multi-page context - work with all pages in the designawait openDesign({ type: "all_pages" }, async (session) => {session.pageRefs.forEach((pageRef) => {console.log(pageRef.type); // => "absolute" or "unsupported"});});
For the time being, apps can only interact with absolute pages (see the following section). All other types of pages are unsupported. The most notable consequence of this is that the Design Editing API isn't compatible with Canva Docs(opens in a new tab or window).
Absolute pages
Absolute pages are pages that have either:
- fixed dimensions (i.e., a width and a height)
- unbounded dimensions (i.e., are infinite)
An example of an absolute page with fixed dimensions is a presentation page. This is the most common type of page in Canva. The only example of a page that has unbounded dimensions is a whiteboard(opens in a new tab or window).
The following code sample demonstrates how to check if the current page has fixed or unbounded dimensions:
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type === "absolute") {if (session.page.dimensions) {console.log("The current page has fixed dimensions.");} else {console.log("The current page has unbounded dimensions.");}}});
API reference for absolute pages: AbsolutePage
Unsupported pages
When a page is of an unsupported type, apps can't read or edit it. Before attempting to interact with a page, always check if the page is unsupported.
In the current_page context, unsupported page types appear as "unsupported":
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type === "unsupported") {console.log("The current page is not supported.");return;}});
In the all_pages context, unsupported pages appear in the pageRefs collection with type "unsupported":
await openDesign({ type: "all_pages" }, async (session) => {session.pageRefs.forEach((pageRef) => {if (pageRef.type === "unsupported") {console.log("This page type is not supported for editing");return;}// pageRef.type will be "absolute" for supported pagesconsole.log(`Page type: ${pageRef.type}`);});});
Locked pages
Pages can be locked.
When a page is locked, its properties and ingredients are read-only. If an app attempts to read or edit a locked page, an error is thrown. Before attempting to edit a page, always check if the page is locked.
In the current_page context:
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.locked) {console.log("The current page is locked, so no edits are allowed.");return;}// Safe to edit the page});
In the all_pages context, check the lock status in the page references before opening pages:
await openDesign({ type: "all_pages" }, async (session) => {for (const pageRef of session.pageRefs.toArray()) {if (pageRef.locked) {console.log(`Page is locked, skipping...`);continue;}await session.helpers.openPage(pageRef, async (pageResult) => {// Safe to edit the page});}});
Multi-page editing
The all_pages design context is currently in preview and only available in the preview package.
The all_pages design context enables apps to work with multiple pages within a design. This unlocks powerful capabilities such as bulk operations across pages and design-wide transformations.
Page references
When using the all_pages context, you work with page references rather than full page snapshots.
import { openDesign } from "@canva/design";await openDesign({ type: "all_pages" }, async (session) => {// Access the catalog of all pagesconst allPageRefs = session.pageRefs.toArray();console.log(`Design has ${allPageRefs.length} pages`);// Each page reference contains essential informationallPageRefs.forEach((pageRef, index) => {console.log(`Page ${index + 1}:`);console.log(` Type: ${pageRef.type}`);console.log(` Locked: ${pageRef.locked}`);});});
Iterating through pages
To edit individual pages, use the openPage helper method. This loads the full page content on-demand.
Only one page can be loaded at a time for performance reasons. You can't call openPage within another openPage callback.
await openDesign({ type: "all_pages" }, async (session) => {for (const pageRef of session.pageRefs.toArray()) {// Check page reference before opening the pageif (pageRef.type !== "absolute" || pageRef.locked) {continue;}await session.helpers.openPage(pageRef, async (pageResult) => {// The pageResult works just like a current_page session but without a sync methodconsole.log(`Editing page with ${pageResult.page.elements.count()} elements`);// Use the same element editing techniques covered in other sectionspageResult.page.elements.forEach((element) => {if (element.type === "text") {console.log(`Found text: ${element.text.readPlaintext()}`);}});});}await session.sync();});
Filtering pages by type
You can filter pages by type before processing them:
await openDesign({ type: "all_pages" }, async (session) => {// Only process unlocked absolute pagesconst editablePageRefs = session.pageRefs.filter((page) => page.type === "absolute" && !page.locked);for (const pageRef of editablePageRefs) {await session.helpers.openPage(pageRef, async (pageResult) => {// Process only the pages we're interested in});}await session.sync();});
Multi-page syncing
When using the all_pages context, syncing works differently to optimize performance and prevent conflicts:
- No syncing within
openPage: Thesyncmethod can't be called inside anopenPagecallback. - Batch syncing: Make edits to multiple pages, then sync once at the end to apply all changes. This follows the same Syncing best practices outlined earlier.
import { openDesign } from "@canva/design";await openDesign({ type: "all_pages" }, async (session) => {// 1. Iterate through pages with `openPage`for (const pageRef of session.pageRefs.toArray()) {if (pageRef.type !== "absolute" || pageRef.locked) {continue;}await session.helpers.openPage(pageRef, async (pageResult) => {// 2. Make edits within each `openPage` callback// (use any element editing techniques from other sections)// Can't call sync() within openPage - this will throw an error// await session.sync(); ❌});}// 3. Call `session.sync()` once at the end to apply all changesawait session.sync();console.log("Changes applied to all pages!");});
Handling unavailable pages
If your app needs to know whether a page was successfully processed, the openPage method returns a status object that indicates the outcome:
{ status: 'executed' } // Page was successfully processed, or{ status: 'skipped', reason: string } // Page was skipped with reason
Here's how you can optionally use the return value:
await openDesign({ type: "all_pages" }, async (session) => {const firstPageRef = session.pageRefs.toArray()[0];// ... some operations that might change the page state ...await session.sync();// Check the result of attempting to open the pageconst result = await session.helpers.openPage(firstPageRef, async (pageResult) => {console.log("Page editing logic");// This callback may or may not execute});if (result.status === 'executed') {console.log("Page was successfully edited");} else {console.log(`Page was skipped: ${result.reason}`);// Handle different scenarios based on the reason}});
Common reasons for skipping include pages being deleted by other users, or other internal state changes that make the page unavailable for editing. Using these return values is optional, but can help create more resilient apps when detailed feedback is needed.
Backgrounds
Backgrounds are base layers that appear behind all other ingredients on a page. The appearance of a background is defined as a fill, which means they can contain colors, images, or videos.
The following code sample demonstrates how to check if a page has a background:
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute") {return;}if (!session.page.background) {console.log("The page doesn't have a background.");return;}console.log(session.page.background); // => Fill});
To learn more about fills, including how to set them, see Fills.
Elements
Elements are the building blocks of every Canva design. This includes shapes, text, images, groups, and more. The Design Editing API provides full CRUD (Create, Read, Update, Delete) capabilities for supported element types.
Supported element operations:
- Creating: Add new elements using state builders.
- Reading: Access element properties and filter by type.
- Updating: Modify positions, styles, and content.
- Deleting: Remove elements from the design.
API reference for element list operations: ElementList
Universal element properties:
All elements share common properties like top, left, width, height, rotation, transparency, and locked. Element-specific properties vary by type (see Element types).
Context compatibility:
Element editing works identically in both design contexts:
current_page: Usesession.pageandsession.helpers.all_pages: UsepageResult.pageandpageResult.helperswithinopenPagecallbacks.
Locked elements
Like pages, elements can be locked. When an element is locked, its properties can be read but not modified. Be sure to check if an element is locked before attempting to modify it:
import { openDesign, type DesignEditing } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute") {return;}const textElement = session.page.elements.toArray().find((element): element is DesignEditing.TextElement => element.type === "text");if (textElement.locked) {console.log("The element is locked and can't be modified.");}});
Creating elements
To create an element, use the element state builder with one of these methods:
createEmbedElement: For external media content.createRectElement: For rectangular shapes with fills.createShapeElement: For custom vector shapes.createTextElement: For formatted text content.
API reference for element state builder: ElementStateBuilder
For example, here's how to create a circular shape element and add it to the current page:
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute" || session.page.locked) {return;}// 1. Create element state using the element state builderconst circleState = session.helpers.elementStateBuilder.createShapeElement({top: 100,left: 350,width: 100,height: 100,viewBox: { top: 0, left: 0, width: 100, height: 100 },paths: [{d: "M 50 0 A 50 50 0 1 1 50 100 A 50 50 0 1 1 50 0 Z", // SVG circle pathfill: { colorContainer: { type: "solid", color: "#0099ff" } }}]});// 2. Add element to the pagesession.page.elements.insertAfter(undefined, circleState);// 3. Sync changes to make them visible in the designawait session.sync();});
Be sure to call the sync method for the change to be reflected in the user's design.
Element layering
The visual layering of elements is determined by their order in the page.elements collection. Elements that appear later in the collection are rendered on top of earlier elements. There's no z-index concept — layering is controlled entirely through element positioning in the collection.
When you add an element using insertAfter(undefined, element), it's inserted at the end of the collection, making it appear on top of all other elements. You can control layering by using insertAfter, insertBefore, moveBefore, and moveAfter methods to position elements relative to each other.
API reference for element list operations: ElementList
Reading elements
To read the elements on the page, use one of the available list methods, such as forEach or filter:
import { openDesign, type DesignEditing } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute") {return;}// Iterate through all elementssession.page.elements.forEach((element, index) => {console.log(`Element ${index}: ${element.type}`);// Read element propertiesconsole.log(`Position: ${element.top}, ${element.left}`);console.log(`Dimensions: ${element.width}x${element.height}`);console.log(`Rotation: ${element.rotation}`);console.log(`Locked: ${element.locked}`);});// Filter specific element typesconst textElements = session.page.elements.filter((element): element is DesignEditing.TextElement => element.type === "text");console.log(`Found ${textElements.length} text elements`);textElements.forEach((textElement) => {const plaintext = textElement.text.readPlaintext();console.log(`Text content: ${plaintext}`);});// Find shapes with specific propertiesconst blueShapes = session.page.elements.filter((element) => {if (element.type !== "shape") return false;return element.paths.toArray().some((path) => {const colorFill = path.fill.colorContainer?.ref;return colorFill?.type === "solid" && colorFill.color === "#0099ff";});});console.log(`Found ${blueShapes.length} blue shapes`);});
Updating elements
You can update elements by modifying their properties directly. Always check if elements are locked before making changes.
Common properties
All elements share these updatable properties:
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute" || session.page.locked) {return;}session.page.elements.forEach((element) => {if (element.type === "unsupported" || element.locked) return; // Skip unsupported or locked elements// Update position, rotation, and transparencyelement.top += 50;element.left += 50;element.rotation += 15;element.transparency = 0.8;});await session.sync();});
Element-specific updates
Different element types have unique properties that can be updated:
// Update rect elementssession.page.elements.forEach((element) => {if (element.type !== "rect" || element.locked) return;// Change fill colorelement.fill.colorContainer.set({type: "solid",color: "#ff6600",});});
// Update text elementssession.page.elements.forEach((element) => {if (element.type !== "text" || element.locked) return;// Replace all text content with formattingconst plaintext = element.text.readPlaintext();element.text.replaceText({ index: 0, length: plaintext.length },"Updated text content",{color: "#0066ff",fontSize: 20,fontWeight: "bold",});});
// Update shape elementssession.page.elements.forEach((element) => {if (element.type !== "shape" || element.locked) return;// Update all path colorselement.paths.forEach((path) => {if (path.fill.colorContainer?.ref?.type === "solid") {path.fill.colorContainer.set({type: "solid",color: "#00ff66",});}});});
// Embed elements have limited update capabilitiessession.page.elements.forEach((element) => {if (element.type !== "embed" || element.locked) return;// Most embed properties are read-only// You can access URL for referenceconsole.log(`Embed URL: ${element.url}`);// But you can still update common propertieselement.top += 10;element.transparency = 0.9;});
Some properties are read-only and can't be modified. Always check the API reference for property mutability.
Deleting elements
To delete elements, call the delete method on an element and sync the changes:
import { openDesign, type DesignEditing } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute" || session.page.locked) {return;}// Delete a specific element by referenceconst elements = session.page.elements.toArray();if (elements.length > 0) {// Delete the first elementsession.page.elements.delete(elements[0]);}// Delete all text elementsconst textElements = session.page.elements.filter((element): element is DesignEditing.TextElement => element.type === "text");textElements.forEach((textElement) => {session.page.elements.delete(textElement);});// Delete elements based on a conditionsession.page.elements.forEach((element) => {// Delete small elements (less than 50x50 pixels)if (element.width < 50 && element.height < 50) {session.page.elements.delete(element);return;}// Delete transparent elementsif (element.transparency && element.transparency > 0.9) {session.page.elements.delete(element);return;}});// Delete all elements except groupsconst nonGroupElements = session.page.elements.filter((element) => element.type !== "group");nonGroupElements.forEach((element) => {session.page.elements.delete(element);});// Apply deletionsawait session.sync();});
Element types
Canva supports a wide variety of elements, a subset of which apps can interact with through the Design Editing API.
The supported element types include:
- Embeds
- Groups
- Rects
- Shapes
- Text
Be aware that, unlike other parts of the SDK:
- The Design Editing API doesn't have a concept of image or video elements. Instead, images are videos are handled as rects with fills. This is more aligned with how Canva works under the hood.
- Text elements are only available as richtext ranges, rather than richtext and plaintext. However, richtext ranges can be converted into plaintext, so it's only one extra step.
- Table elements are not currently supported.
This section covers the unique aspects of the supported element types.
API reference for element types: AbsoluteElement
Embed elements
Embed elements allow you to include rich media content from external sources, such as YouTube videos, Vimeo content, or other embeddable media supported by Iframely(opens in a new tab or window).
Creating embed elements
To create an embed element, provide a URL to embeddable content using the createEmbedElement helper method:
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute" || session.page.locked) {return;}const embedElementState =session.helpers.elementStateBuilder.createEmbedElement({top: 50,left: 50,width: 560,height: 315,rotation: 0,transparency: 0,url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",});session.page.elements.insertAfter(undefined, embedElementState);await session.sync();});
Group elements
Group elements contain other elements and allow them to be moved, rotated, and scaled as a single unit. Groups are essential for organizing complex designs.
Grouping elements
To create a group, first add the elements you want to group to the page, then use the group helper method:
import { openDesign, type DesignEditing } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute" || session.page.locked) {return;}// Create elements to groupconst rectElementState =session.helpers.elementStateBuilder.createRectElement({top: 0,left: 0,width: 100,height: 100,fill: {colorContainer: {type: "solid",color: "#3498db",},},});const textElementState =session.helpers.elementStateBuilder.createTextElement({top: 35,left: 25,width: 50,text: {regions: [{text: "Box",formatting: {fontSize: 16,color: "#ffffff",textAlign: "center",},},],},});// Add elements to snapshotconst addedRect = session.page.elements.insertAfter(undefined,rectElementState);const addedText = session.page.elements.insertAfter(addedRect,textElementState);// Group the elementsconst groupElement = await session.helpers.group({elements: [addedRect, addedText],});await session.sync();});
Ungrouping elements
To ungroup elements, use the ungroup helper method on an existing group:
import { openDesign, type DesignEditing } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute" || session.page.locked) {return;}// Find first unlocked groupconst [groupElement] = session.page.elements.filter((element): element is DesignEditing.GroupElement =>element.type === "group" && !element.locked);if (!groupElement) {console.log("Group element not found in design.");return;}const ungroupedElements = await session.helpers.ungroup({element: groupElement,});console.log(`Ungrouped ${ungroupedElements.length} elements`);await session.sync();});
Error handling
Unlike most async calls in the Design Editing API where errors invalidate the session, group and ungroup operations can be wrapped in try-catch blocks to handle errors gracefully and continue with other operations in the same session.
import { openDesign, type DesignEditing } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute" || session.page.locked) {return;}const groups = session.page.elements.filter((element): element is DesignEditing.GroupElement =>element.type === "group" && !element.locked);for (const group of groups) {try {const ungroupedElements = await session.helpers.ungroup({element: group,});console.log(`Successfully ungrouped ${ungroupedElements.length} elements`);} catch (error) {console.warn(`Failed to ungroup element: ${error.message}`);// Continue processing other groups despite this error}}// Note: sync() follows normal async error behavior// Errors will invalidate the session even with try-catchawait session.sync();});
Iterating through group contents
Groups provide a contents list that allows you to access and iterate through the child elements:
import { openDesign, type DesignEditing } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute") {return;}const groups = session.page.elements.filter((element): element is DesignEditing.GroupElement => element.type === "group");groups.forEach((group) => {// List all element types in the groupconst elementTypes = new Set<string>();group.contents.forEach((child) => {elementTypes.add(child.type);// Child positions are relative to the groupconsole.log(`${child.type} at relative position: ${child.top}, ${child.left}`);});console.log(`Group contains: ${Array.from(elementTypes).join(", ")}`);});});
Rect elements
Rect elements are rectangular shapes that can be filled with colors, images, or videos.
Creating rects with color fills
To create a rect with a solid color fill, use the colorContainer property:
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute" || session.page.locked) {return;}const rectElementState =session.helpers.elementStateBuilder.createRectElement({top: 50,left: 50,width: 200,height: 150,rotation: 0,transparency: 0,fill: {colorContainer: {type: "solid",color: "#ff6b6b",},},});session.page.elements.insertAfter(undefined, rectElementState);await session.sync();});
Creating rects with image fills
To create a rect with an image fill, upload an image asset before setting that asset as the fill:
import { upload } from "@canva/asset";import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute" || session.page.locked) {return;}const uploadResult = await upload({type: "image",url: "https://example.com/my-image.jpg",mimeType: "image/jpeg",thumbnailUrl: "https://example.com/my-image-thumbnail.jpg",aiDisclosure: "none",});const rectElementState =session.helpers.elementStateBuilder.createRectElement({top: 50,left: 50,width: 300,height: 200,fill: {mediaContainer: {type: "image",imageRef: uploadResult.ref,flipX: false,flipY: false,},},});session.page.elements.insertAfter(undefined, rectElementState);await session.sync();});
Creating rects with video fills
To create a rect with a video fill, upload a video asset before setting that asset as the fill:
import { upload } from "@canva/asset";import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute" || session.page.locked) {return;}const uploadResult = await upload({type: "video",url: "https://example.com/my-video.mp4",mimeType: "video/mp4",thumbnailImageUrl: "https://example.com/video-thumbnail.jpg",thumbnailVideoUrl: "https://example.com/video-preview.mp4",aiDisclosure: "none",});const rectElementState =session.helpers.elementStateBuilder.createRectElement({top: 50,left: 50,width: 300,height: 200,fill: {mediaContainer: {type: "video",videoRef: uploadResult.ref,flipX: false,flipY: false,},},});session.page.elements.insertAfter(undefined, rectElementState);await session.sync();});
Shape elements
Shape elements are vector graphics defined by SVG-like paths. They support multiple paths with individual fills and strokes, enabling complex vector artwork.
Creating shape elements
To create a shape element, define one or more paths using SVG path syntax:
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute" || session.page.locked) {return;}const shapeElementState =session.helpers.elementStateBuilder.createShapeElement({top: 100,left: 100,width: 100,height: 100,viewBox: {top: 0,left: 0,width: 100,height: 100,},paths: [{d: "M 50 0 A 50 50 0 1 1 50 100 A 50 50 0 1 1 50 0 Z",fill: {colorContainer: {type: "solid",color: "#3498db",},},},],});session.page.elements.insertAfter(undefined, shapeElementState);await session.sync();});
Text elements
Text elements render formatted text content using richtext ranges, which support various formatting options and paragraph-level styling.
Creating text elements
To create a text element, define one or more text regions with content and formatting:
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute" || session.page.locked) {return;}const textElementState =session.helpers.elementStateBuilder.createTextElement({top: 50,left: 50,width: 300,rotation: 0,transparency: 0,text: {regions: [{text: "Hello, Canva!",formatting: {fontSize: 32,color: "#2c3e50",fontWeight: "bold",textAlign: "center",},},],},});session.page.elements.insertAfter(undefined, textElementState);await session.sync();});
Fills
A fill defines the interior appearance of a design ingredient. For example, apps can set the background of a page by setting its background to an image fill.
API reference for fills: Fill
Ingredient types
The following types of ingredients can have fills:
- Backgrounds
- Rects
- Shapes
The way that fills are read and edited is consistent across each of these different types.
Fill types
The following types of fills are supported:
- Colors
- Images
- Videos
In the future, gradient fills will also be supported.
Reading fills
To check if an element has a color fill and read its value:
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute") {return;}// Read page background colorif (session.page.background?.colorContainer?.ref?.type === "solid") {const color = session.page.background.colorContainer.ref.color;console.log(`Background color: ${color}`);}// Read rect element colorssession.page.elements.forEach((element) => {if (element.type !== "rect") {return;}const colorFill = element.fill.colorContainer?.ref;if (colorFill?.type === "solid") {console.log(`Rect color: ${colorFill.color}`);}});// Read shape path colorssession.page.elements.forEach((element) => {if (element.type !== "shape") {return;}element.paths.forEach((path, index) => {const colorFill = path.fill.colorContainer?.ref;if (colorFill?.type === "solid") {console.log(`Path ${index} color: ${colorFill.color}`);}});});});
To check for image fills and access their properties:
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute") {return;}// Read page background imageconst bgMedia = session.page.background?.mediaContainer?.ref;if (bgMedia?.type === "image") {console.log(`Background image ref: ${bgMedia.imageRef}`);}// Read rect element imagessession.page.elements.forEach((element) => {if (element.type !== "rect") {return;}const media = element.fill.mediaContainer?.ref;if (media?.type === "image") {console.log(`Rect has image fill: ${media.imageRef}`);}});});
To check for video fills and access their properties:
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute") {return;}// Read page background videoconst bgMedia = session.page.background?.mediaContainer?.ref;if (bgMedia?.type === "video") {console.log(`Background video ref: ${bgMedia.videoRef}`);}// Read shape path videossession.page.elements.forEach((element) => {if (element.type !== "shape") {return;}element.paths.forEach((path, index) => {const media = path.fill.mediaContainer?.ref;if (media?.type === "video") {console.log(`Path ${index} has video fill: ${media.videoRef}`);}});});});
Setting fills
When using color hex codes in colorContainer, all letters must be lowercase.
Color fills are the simplest type of fill, consisting of a solid color specified as a hex code:
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute" || session.page.locked) {return;}// Set page background to a solid colorif (session.page.background) {session.page.background.colorContainer.set({type: "solid",color: "#f0f0f0",});}// Set rect element fills to colorssession.page.elements.forEach((element) => {if (element.type !== "rect" || element.locked) {return;}element.fill.colorContainer.set({type: "solid",color: "#ff00ff",});});// Set different colors for shape pathssession.page.elements.forEach((element) => {if (element.type !== "shape" || element.locked) {return;}element.paths.forEach((path, index) => {const colors = ["#ff0000", "#00ff00", "#0000ff"];path.fill.colorContainer.set({type: "solid",color: colors[index % colors.length],});});});await session.sync();});
Image fills require uploading an image asset first, then using the image reference:
import { upload } from "@canva/asset";import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute" || session.page.locked) {return;}// Upload an image assetconst imageAsset = await upload({type: "image",url: "https://example.com/background.jpg",mimeType: "image/jpeg",thumbnailUrl: "https://example.com/background-thumb.jpg",aiDisclosure: "none",});// Set page background to an imageif (session.page.background) {session.page.background.mediaContainer.set({type: "image",imageRef: imageAsset.ref,flipX: false,flipY: false,});}// Set rect fills to imagessession.page.elements.forEach((element) => {if (element.type !== "rect" || element.locked) {return;}element.fill.mediaContainer.set({type: "image",imageRef: imageAsset.ref,flipX: true, // Flip horizontallyflipY: false,});});await session.sync();});
Video fills work similarly to image fills but use video assets:
import { upload } from "@canva/asset";import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute" || session.page.locked) {return;}// Upload a video assetconst videoAsset = await upload({type: "video",url: "https://example.com/background-video.mp4",mimeType: "video/mp4",thumbnailImageUrl: "https://example.com/video-thumb.jpg",aiDisclosure: "none",});// Set page background to a videoif (session.page.background) {session.page.background.mediaContainer.set({type: "video",videoRef: videoAsset.ref,flipX: false,flipY: false,});}// Set shape fills to videos (if editable)session.page.elements.forEach((element) => {if (element.type !== "shape" || element.locked) {return;}element.paths.forEach((path) => {if (!path.fill.isMediaEditable) {return;}path.fill.mediaContainer.set({type: "video",videoRef: videoAsset.ref,flipX: false,flipY: false,});});});await session.sync();});
Clearing fills
To remove fills and make elements transparent, set the fill to undefined:
import { openDesign } from "@canva/design";await openDesign({ type: "current_page" }, async (session) => {if (session.page.type !== "absolute" || session.page.locked) {return;}// Clear all fills from rect elementssession.page.elements.forEach((element) => {if (element.type !== "rect" || element.locked) {return;}// Remove both color and media fillselement.fill.colorContainer.set(undefined);element.fill.mediaContainer.set(undefined);});// Clear only media fill from page background (keep color)if (session.page.background) {session.page.background.mediaContainer.set(undefined);}await session.sync();});