Design Editing
Apps can traverse the elements in a design and then read or update their structure and content. This allows apps to analyze and manipulate designs in a variety of ways, such as by rearranging all of the content on a page.
Prerequisites
The APIs documented on this page require the beta version of @canva/design
package:
npm install @canva/design@beta
Element types
Apps can traverse the following types of elements:
While traversing, there's no such thing as an "image" or "video" element. Instead, there's only rect elements, which may contain video or image content. (In the future, we hope to resolve this inconsistency.)
Other types of elements, such as tables, are not supported.
Design contexts
App must specify a context that indicates which part of the design should be traversed.
For the time being, apps can only traverse elements on the current page. In the future, additional contexts will be supported, such as specific pages, ranges of pages, and more.
It's not possible for apps to traverse elements across an entire design.
Reading designs
To read the structure of a design, call the openDesign
method:
import { openDesign } from "@canva/design";import { Button, Rows } from "@canva/app-ui-kit";import * as styles from "styles/components.css";export function App() {function handleClick() {openDesign({ type: "current_page" }, async (draft) => {console.log(draft);});}return (<div className={styles.scrollContainer}><Rows spacing="2u"><Button variant="primary" onClick={handleClick}>Read design</Button></Rows></div>);}
This method accepts a context and a function that receives a draft object.
This draft is a snapshot that contains information about the specified part of the design, such as its pages and elements on those pages. The available information depends on the context.
Reading pages
When the context is the current page, the draft contains a page
object:
import { openDesign } from "@canva/design";import { Button, Rows } from "@canva/app-ui-kit";import * as styles from "styles/components.css";export function App() {function handleClick() {openDesign({ type: "current_page" }, async (draft) => {console.log(draft.page);});}return (<div className={styles.scrollContainer}><Rows spacing="2u"><Button variant="primary" onClick={handleClick}>Read design</Button></Rows></div>);}
There are different types of pages, each of which exposes different properties.
For the time being, apps can only read the structure and content of fixed pages. All other types of pages have a type
of "unsupported"
and don't expose any additional information.
Reading elements
A page may contain zero or more elements:
import { openDesign } from "@canva/design";import { Button, Rows } from "@canva/app-ui-kit";import * as styles from "styles/components.css";export function App() {function handleClick() {openDesign({ type: "current_page" }, async (draft) => {console.log(draft.page.elements);});}return (<div className={styles.scrollContainer}><Rows spacing="2u"><Button variant="primary" onClick={handleClick}>Read design</Button></Rows></div>);}
These elements are exposed as a list and this list provides methods for interacting with the elements in a variety of ways, such as by counting or filtering them:
import { openDesign } from "@canva/design";import { Button, Rows } from "@canva/app-ui-kit";import * as styles from "styles/components.css";export function App() {function handleClick() {openDesign({ type: "current_page" }, async (draft) => {draft.page.elements.forEach((element, index) => {console.log(index, element);});});}return (<div className={styles.scrollContainer}><Rows spacing="2u"><Button variant="primary" onClick={handleClick}>Read design</Button></Rows></div>);}
The order of the elements in the list determines the order in which the elements are rendered. For example, elements later in the list are rendered in front of elements earlier in the list.
The elements themselves share some common properties, such as dimensions, while also having properties that are unique to their type:
import { openDesign } from "@canva/design";import { Button, Rows } from "@canva/app-ui-kit";import * as styles from "styles/components.css";export function App() {function handleClick() {openDesign({ type: "current_page" }, async (draft) => {draft.page.elements.forEach((element, index) => {// Properties specific to typesif (element.type === "embed") {console.log(element.url);}// General propertiesconsole.log(element.width);});});}return (<div className={styles.scrollContainer}><Rows spacing="2u"><Button variant="primary" onClick={handleClick}>Read design</Button></Rows></div>);}
Updating elements
Apps can manipulate elements by calling methods exposed on the lists:
import { openDesign } from "@canva/design";import { Button, Rows } from "@canva/app-ui-kit";import * as styles from "styles/components.css";import type { DesignEditing } from "@canva/design";export function App() {function handleClick() {openDesign({ type: "current_page" }, async (draft, { elementBuilder }) => {if (draft.page.type !== "fixed") {return;}// Inserting a new rectangle element using list methodsconst newRect = elementBuilder.createRectElement({top: 100,left: 100,width: 150,height: 100,fill: {color: {type: "solid",color: "#ff0000",},},});// Insert the new rectangle at the end of the elements listdraft.page.elements.insertAfter(undefined, newRect);});}return (<div className={styles.scrollContainer}><Rows spacing="2u"><Button variant="primary" onClick={handleClick}>Insert rectangle</Button></Rows></div>);}
...or by overwriting the properties of individual elements:
import { openDesign } from "@canva/design";import { Button, Rows } from "@canva/app-ui-kit";import * as styles from "styles/components.css";export function App() {function handleClick() {openDesign({ type: "current_page" }, async (draft) => {if (draft.page.type !== "fixed") {return;}// Changing the fill color of all rectangle elementsdraft.page.elements.forEach((element) => {if (element.type === "rect") {// Overwrite the fill color propertyelement.fill.color = {type: "solid",color: "#00ff00",};}});});}return (<div className={styles.scrollContainer}><Rows spacing="2u"><Button variant="primary" onClick={handleClick}>Change rectangle colors</Button></Rows></div>);}
Saving changes
Any changes made to the draft object are not automatically reflected in the design. For the changes to be reflected, the app needs to explicitly save the changes that it makes.
To save changes to the draft, call the save
method that's exposed by the draft object:
import { openDesign } from "@canva/design";import { Button, Rows } from "@canva/app-ui-kit";import * as styles from "styles/components.css";export function App() {function handleClick() {openDesign({ type: "current_page" }, async (draft) => {if (draft.page.type !== "fixed") {return;}// Moving all elements 50 pixels to the right and 50 pixels downdraft.page.elements.forEach((element) => {element.left += 50;element.top += 50;console.log(`Moved element to (${element.left}, ${element.top}).`);});return await draft.save();});}return (<div className={styles.scrollContainer}><Rows spacing="2u"><Button variant="primary" onClick={handleClick}>Move elements</Button></Rows></div>);}
Once this method is called, no further changes can be made without calling the openDesign
method again.
Code samples
Counting elements
import { openDesign } from "@canva/design";import { Button, Rows } from "@canva/app-ui-kit";import * as styles from "styles/components.css";export function App() {function handleClick() {openDesign({ type: "current_page" }, async (draft) => {if (draft.page.type !== "fixed") {return;}const elementCount = draft.page.elements.count();console.log(`Total elements on the page: ${elementCount}`);});}return (<div className={styles.scrollContainer}><Rows spacing="2u"><Button variant="primary" onClick={handleClick}>Count elements</Button></Rows></div>);}
Iterating elements
import { openDesign } from "@canva/design";import { Button, Rows } from "@canva/app-ui-kit";import * as styles from "styles/components.css";export function App() {function handleClick() {openDesign({ type: "current_page" }, async (draft) => {if (draft.page.type !== "fixed") {return;}draft.page.elements.forEach((element, index) => {console.log(`Element ${index + 1}: Type=${element.type}, Position=(${element.left}, ${element.top})`);});});}return (<div className={styles.scrollContainer}><Rows spacing="2u"><Button variant="primary" onClick={handleClick}>Iterate elements</Button></Rows></div>);}
Filtering elements
import { openDesign } from "@canva/design";import { Button, Rows } from "@canva/app-ui-kit";import * as styles from "styles/components.css";import type { DesignEditing } from "@canva/design";export function App() {function handleClick() {openDesign({ type: "current_page" }, async (draft) => {if (draft.page.type !== "fixed") {return;}const textElements =draft.page.elements.filter<DesignEditing.TextElement>((el) => el.type === "text");console.log(`Found ${textElements.length} text element(s):`);textElements.forEach((el, index) => {console.log(`Text Element ${index + 1}: "${el.text.text}" at (${el.left}, ${el.top})`);});});}return (<div className={styles.scrollContainer}><Rows spacing="2u"><Button variant="primary" onClick={handleClick}>Filter text elements</Button></Rows></div>);}
Inserting elements
import { openDesign } from "@canva/design";import { Button, Rows } from "@canva/app-ui-kit";import * as styles from "styles/components.css";import type { DesignEditing } from "@canva/design";export function App() {function handleClick() {openDesign({ type: "current_page" }, async (draft, { elementBuilder }) => {if (draft.page.type !== "fixed") {return;}// Define rectangle propertiesconst rectOpts: DesignEditing.CreateRectElementOpts = {top: 150,left: 150,width: 200,height: 100,fill: {color: {type: "solid",color: "#00ff00",},},};// Create the rectangle elementconst rectElement = elementBuilder.createRectElement(rectOpts);// Insert the rectangle at the end of the elements listdraft.page.elements.insertAfter(undefined, rectElement);// Save the changesreturn await draft.save();});}return (<div className={styles.scrollContainer}><Rows spacing="2u"><Button variant="primary" onClick={handleClick}>Insert rectangle</Button></Rows></div>);}
Moving elements
import { openDesign } from "@canva/design";import { Button, Rows } from "@canva/app-ui-kit";import * as styles from "styles/components.css";export function App() {function handleClick() {openDesign({ type: "current_page" }, async (draft) => {if (draft.page.type !== "fixed") {return;}draft.page.elements.forEach((element) => {element.left += 50;element.top += 50;console.log(`Moved element to (${element.left}, ${element.top})`);});// Save the changesconsole.log("All elements have been moved.");return await draft.save();});}return (<div className={styles.scrollContainer}><Rows spacing="2u"><Button variant="primary" onClick={handleClick}>Move elements</Button></Rows></div>);}
Deleting elements
import { openDesign } from "@canva/design";import { Button, Rows } from "@canva/app-ui-kit";import * as styles from "styles/components.css";import type { DesignEditing } from "@canva/design";export function App() {function handleClick() {openDesign({ type: "current_page" }, async (draft) => {if (draft.page.type !== "fixed") {return;}const textElements =draft.page.elements.filter<DesignEditing.TextElement>((el) => el.type === "text");textElements.forEach((el) => {draft.page.elements.delete(el);console.log(`Deleted text element at (${el.left}, ${el.top})`);});// Save the changesconsole.log("All text elements have been deleted.");return await draft.save();});}return (<div className={styles.scrollContainer}><Rows spacing="2u"><Button variant="primary" onClick={handleClick}>Delete text elements</Button></Rows></div>);}