Reading elements
When a user selects certain types of elements, an app can detect the selection of those elements and read content from them. This unlocks a number of powerful features, such as tools for replacing elements.
For the time being, apps cannot arbitrarily read data from a design. They can only read certain types of content from the user's current selection.
Supported content types
Apps can read the following types of content from selected elements:
- Raster images
- Text, as plaintext or richtext
- Videos
Be aware that different types of elements may contain the same types of content. For example, both image elements and page backgrounds contain image content that can be read by an app.
In the future, we intend to support more types of content.
How to read elements
Step 1: Enable the required permissions
In the Developer Portal, enable the canva:design:content:read
permission. In the future, the Apps SDK will throw an error if the required permissions are not enabled. To learn more, see Configuring permissions.
Step 2: Listen for selection events
When a user selects one or more elements, Canva emits a selection event. This event contains information about the selected elements. You can listen for selection events with a React hook (recommended) or manually.
To listen for selection events with the React hook:
-
Import the
useSelection
hook from the starter kit'sutils
directory:import { useSelection } from "utils/use_selection_hook"; // https://github.com/canva-sdks/canva-apps-sdk-starter-kit/blob/main/utils/use_selection_hook.tsts -
Call the hook, passing in a type of content as the only argument:
const currentSelection = useSelection("plaintext");tsThe argument must be one of the following values:
"image"
"plaintext"
"video"
The hook returns the selection event.
You can then access the selection event via the currentSelection
variable:
console.log(currentSelection);
Step 3: Check if an element is selected
The selection event contains a count
property that contains the number of selected elements. When an element isn't selected, this value is 0
. You can use the count
property to check if an element is currently selected:
const isElementSelected = currentSelection.count > 0;
This is useful for updating the user interface in response to the user's selection — for example, by disabling a button if an element isn't selected:
<Button variant="primary" disabled={!isElementSelected}>Click me</Button>
Step 4: Read the contents of the element
The selection event has a read
method that returns an object with a contents
property. This property contains an array of objects. Each object represents an individual piece of content, such as the image used for a page background.
Images
If the selection event contains image content and the change handler is scoped to "image"
, each object contains a ref
property. This property contains a unique identifier that points to an asset in Canva's backend:
const draft = await currentSelection.read();for (const content of draft.contents) {console.log(content.ref);}
The value of the ref
property is an opaque string. This means it's not intended to be read or manipulated. You can, however, convert the ref
into a URL and then download the image data from that URL.
To convert the ref
into a URL:
-
Import the
getTemporaryUrl
method from the@canva/asset
package:import { getTemporaryUrl } from "@canva/asset";ts -
Call the method, passing in the
ref
and the type of asset:const { url } = await getTemporaryUrl({type: "image",ref: content.ref,});console.log(url);ts
Plaintext
If the selection event contains plaintext content, each object contains the plaintext of the element:
const draft = await currentSelection.read();for (const content of draft.contents) {console.log(content.text);}
All formatting is stripped out, but the text may contain line breaks.
Richtext
If the selection event contains richtext content, each object is a richtext range that exposes a variety of methods for interacting with that particular portion of richtext:
const draft = await currentSelection.read();for (const content of draft.contents) {// Get the text with formatting informationconst regions = content.readTextRegions();for (const region of regions) {// The plaintext content of a regionconsole.log(region.text);// The formatting information of a regionconsole.log(region.formatting);}}
To learn more, see Richtext ranges.
Videos
If the selection event contains video content and the change handler is scoped to "video"
, each object contains a ref
property. This property contains a unique identifier that points to an asset in Canva's backend:
const draft = await currentSelection.read();for (const content of draft.contents) {console.log(content.ref);}
The value of the ref
property is an opaque string. This means it's not intended to be read or manipulated. You can, however, convert the ref
into a URL and then download the video data from that URL.
To convert the ref
into a URL:
-
Import the
getTemporaryUrl
method from the@canva/asset
package:import { getTemporaryUrl } from "@canva/asset";ts -
Call the method, passing in the
ref
and the type of asset:const { url } = await getTemporaryUrl({type: "video",ref: content.ref,});console.log(url);ts
Gotchas
- Apps can only read the selection of raster images, not vector images.
API reference
Code sample
Image
import React from "react";import { Button } from "@canva/app-ui-kit";import { useSelection } from "utils/use_selection_hook"; // https://github.com/canva-sdks/canva-apps-sdk-starter-kit/blob/main/utils/use_selection_hook.tsimport { getTemporaryUrl } from "@canva/asset";import styles from "styles/components.css";export function App() {const currentSelection = useSelection("image");const isElementSelected = currentSelection.count > 0;async function handleClick() {if (!isElementSelected) {return;}const draft = await currentSelection.read();for (const content of draft.contents) {const { url } = await getTemporaryUrl({type: "image",ref: content.ref,});console.log(url);}}return (<div className={styles.scrollContainer}><Buttonvariant="primary"disabled={!isElementSelected}onClick={handleClick}>Read selected image content</Button></div>);}
Plaintext
import React from "react";import { Button } from "@canva/app-ui-kit";import { useSelection } from "utils/use_selection_hook"; // https://github.com/canva-sdks/canva-apps-sdk-starter-kit/blob/main/utils/use_selection_hook.tsimport styles from "styles/components.css";export function App() {const currentSelection = useSelection("plaintext");const isElementSelected = currentSelection.count > 0;async function handleClick() {if (!isElementSelected) {return;}const draft = await currentSelection.read();for (const content of draft.contents) {console.log(content.text);}}return (<div className={styles.scrollContainer}><Buttonvariant="primary"disabled={!isElementSelected}onClick={handleClick}>Read selected plaintext content</Button></div>);}
Richtext
import React from "react";import { Button } from "@canva/app-ui-kit";import { selection, SelectionEvent } from "@canva/design";import styles from "styles/components.css";export function App() {const [currentSelection, setCurrentSelection] = React.useState<SelectionEvent<"richtext"> | undefined>();const isElementSelected = (currentSelection?.count ?? 0) > 0;React.useEffect(() => {return selection.registerOnChange({scope: "richtext",onChange: setCurrentSelection,});}, []);async function handleClick() {if (!isElementSelected || !currentSelection) {return;}const draft = await currentSelection.read();for (const content of draft.contents) {// Get the text with formatting informationconst regions = content.readTextRegions();for (const region of regions) {// The plaintext content of a regionconsole.log(region.text);// The formatting information of a regionconsole.log(region.formatting);}}}return (<div className={styles.scrollContainer}><Buttonvariant="primary"disabled={!isElementSelected}onClick={handleClick}stretch>Read selected richtext content</Button></div>);}
Video
import React from "react";import { Button } from "@canva/app-ui-kit";import { useSelection } from "utils/use_selection_hook"; // https://github.com/canva-sdks/canva-apps-sdk-starter-kit/blob/main/utils/use_selection_hook.tsimport { getTemporaryUrl } from "@canva/asset";import styles from "styles/components.css";export function App() {const currentSelection = useSelection("video");const isElementSelected = currentSelection.count > 0;async function handleClick() {if (!isElementSelected) {return;}const draft = await currentSelection.read();for (const content of draft.contents) {const { url } = await getTemporaryUrl({type: "video",ref: content.ref,});console.log(url);}}return (<div className={styles.scrollContainer}><Buttonvariant="primary"disabled={!isElementSelected}onClick={handleClick}>Read selected video content</Button></div>);}