Querying content
Apps can query the content in a user's design, similar to how the HTML DOM can be queried. They can then read and update the queried content, which unlocks a range of powerful use-cases.
Prerequisites
The APIs documented on this page require the beta version of @canva/design
package:
npm install @canva/design@beta
Content types
There are different types of content that may appear in a design, including:
For the time being, richtext is the only content type that's compatible with querying.
In the future, other content types will be supported.
Contexts
When an app queries a design, it must specify a context. This determines what part of the design to query. For the time being, the only supported context is the current page. In the future, other contexts will be supported.
Reading content
Apps can register a callback for any combination of the supported content types and contexts:
import { readContent } from "@canva/design";// Read richtext content on current pageawait readContent({contentType: "richtext",context: "current_page",},async (draft) => {console.log(draft);});
This callback receives a snapshot of the queried content, known as the draft.
A single design may contain multiple pieces of content and each piece of queried content exists within this draft. You'll typically want to loop through the individual content items:
import { readContent } from "@canva/design";// Read richtext content on current pageawait readContent({contentType: "richtext",context: "current_page",},async (draft) => {// Loop through content itemsfor (const content of draft.contents) {console.log(content);}});
Each content item exposes methods and properties for reading and updating the content. The available methods and properties depend on the type of content.
In the case of richtext content, each item is a richtext range:
import { readContent } from "@canva/design";// Read richtext content on current pageawait readContent({contentType: "richtext",context: "current_page",},async (draft) => {// Loop through content itemsfor (const content of draft.contents) {// Each content item is a richtext rangeconst regions = content.readTextRegions();console.log(regions);}});
To learn more, see Richtext ranges.
Updating content
Each content item exposes methods and properties that can be used to update the queried content:
import { readContent } from "@canva/design";await readContent({contentType: "richtext",context: "current_page",},async (draft) => {// Loop through each content itemfor (const content of draft.contents) {// Get the richtext content as plaintextconst plaintext = content.readPlaintext();// Format the richtextcontent.formatParagraph({ index: 0, length: plaintext.length },{ fontWeight: "bold" });}});
But before a change is reflected in the user's design, the draft must be synced:
import { readContent } from "@canva/design";await readContent({contentType: "richtext",context: "current_page",},async (draft) => {// Loop through each content itemfor (const content of draft.contents) {// Get the richtext content as plaintextconst plaintext = content.readPlaintext();// Format the richtextcontent.formatParagraph({ index: 0, length: plaintext.length },{ fontWeight: "bold" });}// Sync the content so that it's reflected in the designawait draft.sync();});
Syncing a draft:
- Commits the changes to the user's design.
- Updates the draft so that it contains the modified content.
Any changes that are not synced will be discarded.
Handling conflicts
If queried content is changed after the readContent
method is called but before the draft is synced, it can lead to conflicts between the state of the draft and the true state of the design.
Canva will attempt to resolve conflicts, but we can't make any guarantees about how conflicts will be resolved. To minimize the risk of unexpected behavior, we recommend syncing the draft as often as possible. This will reduce the size and complexity of conflicts, leading to more predictable behavior.
There's no limit to how many times a draft can be synced.
Handling deleted content
If a queried content item is deleted after the readContent
method is called, the deleted content will continue to exist within the draft, even after the draft is synced.
To identify deleted content, each content item has a deleted
property that is either true
or false
. You can use this property to avoid operating on content that no longer exists in the user's design:
import { readContent } from "@canva/design";// Read richtext content on current pageawait readContent({contentType: "richtext",context: "current_page",},async (draft) => {// Loop through the individual content itemsfor (const content of draft.contents) {// Ignore deleted contentif (content.deleted) {continue;}// Get the richtext content as plaintextconst plaintext = content.readPlaintext();// Format the richtextcontent.formatParagraph({ index: 0, length: plaintext.length },{ fontWeight: "bold" });}// Sync the content so that it's reflected in the designawait draft.sync();});
Gotchas
- You can't use querying to read or update the position of elements on a page. You can only use querying to read and update the content of a design, not its structure.
- In a content draft, the order of the content items is not guaranteed. This means an app shouldn't rely on the order of the items being consistent or predictable.
- You can't call the
readContent
method from within the callback of anotherreadContent
method — that is, the calls can't be nested. If you try to do this, an error will be thrown.
Example
import { Button, Rows } from "@canva/app-ui-kit";import { readContent } from "@canva/design";import styles from "styles/components.css";export function App() {async function handleClick() {// Read richtext content on current pageawait readContent({contentType: "richtext",context: "current_page",},async (draft) => {// Loop through each content itemfor (const content of draft.contents) {// Get the richtext content as plaintextconst plaintext = content.readPlaintext();// Format the richtextcontent.formatParagraph({ index: 0, length: plaintext.length },{ fontWeight: "bold" });}// Sync the content so that it's reflected in the designawait draft.sync();});}return (<div className={styles.scrollContainer}><Rows spacing="2u"><Button variant="primary" onClick={handleClick}>Update richtext content</Button></Rows></div>);}