On September 25th, 2024, we released v2 of the Apps SDK. To learn what’s new and how to upgrade, see Migration FAQ and Migration guide.

Querying content

Reading and updating content in a user's design.

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.

Content is not the same as an element in a design. However, they are related because elements contain content. An app can operate on content through content querying, but needs design editing to position elements and set element dimensions. Even if design editing can reach content in some instances, content querying operates consistently on the content contained within elements. See Design editing for more information, and the Design Editing API guidelines.

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.

Targets

When an app queries a design, it must specify a target. This determines what part of the design to query. For the time being, the only supported target is the current page. In the future, other targets will be supported.

Reading content

Apps can register a callback for any combination of the supported content types and targets:

import { editContent } from "@canva/design";
// Read and edit richtext content on current page
await editContent(
{
contentType: "richtext",
target: "current_page",
},
async (session) => {
console.log(session.contents);
}
);
TSX

This callback receives a session object that contains both a sync method for managing changes and a contents property containing an array of content items that can be read or modified.

A single design may contain multiple pieces of content. You'll typically want to loop through the individual content items:

import { editContent } from "@canva/design";
// Read and edit richtext content on current page
await editContent(
{
contentType: "richtext",
target: "current_page",
},
async (session) => {
// Loop through content items
for (const content of session.contents) {
console.log(content);
}
}
);
TSX

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 { editContent } from "@canva/design";
// Read and edit richtext content on current page
await editContent(
{
contentType: "richtext",
target: "current_page",
},
async (session) => {
// Loop through content items
for (const content of session.contents) {
// Each content item is a richtext range
const regions = content.readTextRegions();
console.log(regions);
}
}
);
TSX

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 { editContent } from "@canva/design";
await editContent(
{
contentType: "richtext",
target: "current_page",
},
async (session) => {
// Loop through each content item
for (const content of session.contents) {
// Get the richtext content as plaintext
const plaintext = content.readPlaintext();
// Format the richtext
content.formatParagraph(
{ index: 0, length: plaintext.length },
{ fontWeight: "bold" }
);
}
// Sync the content so that it's reflected in the design
await session.sync();
}
);
TSX

But before a change is reflected in the user's design, the session must be synced:

import { editContent } from "@canva/design";
await editContent(
{
contentType: "richtext",
target: "current_page",
},
async (session) => {
// Loop through each content item
for (const content of session.contents) {
// Get the richtext content as plaintext
const plaintext = content.readPlaintext();
// Format the richtext
content.formatParagraph(
{ index: 0, length: plaintext.length },
{ fontWeight: "bold" }
);
}
// Sync the content so that it's reflected in the design
await session.sync();
}
);
TSX

Syncing a session:

  • Commits the changes to the user's design.
  • Updates the content items so they contain the modified content.

Be aware that:

  • Any unsynced changes will be discarded.
  • A synced session will not contain new content items that have been created since the editContent method was called. To access new content items, an app must make another call to editContent.
  • When editing content, there's a one-minute timeout to avoid the snapshot of the content from becoming too stale. This timeout is reset when the session is synced, so we recommend syncing the session as often as possible.

Handling conflicts

If queried content is changed after the editContent method is called but before the session is synced, it can lead to conflicts between the state of the content 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 session 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 session can be synced.

Handling deleted content

If a queried content item is deleted after the editContent method is called, the deleted content will continue to exist within the content array, even after the session 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 { editContent } from "@canva/design";
// Read and edit richtext content on current page
await editContent(
{
contentType: "richtext",
target: "current_page",
},
async (session) => {
// Loop through the individual content items
for (const content of session.contents) {
// Ignore deleted content
if (content.deleted) {
continue;
}
// Get the richtext content as plaintext
const plaintext = content.readPlaintext();
// Format the richtext
content.formatParagraph(
{ index: 0, length: plaintext.length },
{ fontWeight: "bold" }
);
}
// Sync the content so that it's reflected in the design
await session.sync();
}
);
TSX

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. Use the Design Editing features to reposition design elements.
  • In content arrays, 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 editContent method from within the callback of another editContent 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 { editContent } from "@canva/design";
import styles from "styles/components.css";
export function App() {
async function handleClick() {
// Read and edit richtext content on current page
await editContent(
{
contentType: "richtext",
target: "current_page",
},
async (session) => {
// Loop through each content item
for (const content of session.contents) {
// Get the richtext content as plaintext
const plaintext = content.readPlaintext();
// Format the richtext
content.formatParagraph(
{ index: 0, length: plaintext.length },
{ fontWeight: "bold" }
);
}
// Sync the content so that it's reflected in the design
await session.sync();
}
);
}
return (
<div className={styles.scrollContainer}>
<Rows spacing="2u">
<Button variant="primary" onClick={handleClick}>
Update richtext content
</Button>
</Rows>
</div>
);
}
TSX