> ## Documentation Index > Fetch the complete documentation index at: https://www.canva.dev/docs/apps/llms.txt > Use this file to discover all available pages before exploring further. # selection.registerOnChange API reference for the selection.registerOnChange method. Registers a callback that runs when the specified type of content is selected. * This callback fires immediately if content is already selected when the callback is registered. * App elements are not supported by this API. For managing selection and updates to app elements, please refer to the `initAppElement` API call for details and examples. ## Usage ### Handling plaintext selection ```typescript import { selection } from "@canva/design"; selection.registerOnChange({ scope: 'plaintext', onChange: async (event) => { if (event.count > 0) { const draft = await event.read(); // Do something with the selected text, e.g. `draft.contents[0].text` } } }); ``` ### Handling image selection ```typescript import { selection } from "@canva/design"; selection.registerOnChange({ scope: 'image', onChange: async (event) => { if (event.count > 0) { const draft = await event.read(); // Do something with the selected image ref, e.g. `draft.contents[0].ref` } } }); ``` ### Edit selected image and upload with parentRef ```typescript import { selection } from "@canva/design"; import { getTemporaryUrl, upload } from "@canva/asset"; selection.registerOnChange({ scope: 'image', onChange: async (event) => { if (event.count > 0) { const draft = await event.read(); for (const content of draft.contents) { // Fetch a temporary URL to the original image const { url } = await getTemporaryUrl({ type: "image", ref: content.ref }); // Apply any edits you want to the image const processedImageUrl = await applyImageEffect(url); // Upload the edited image with parentRef const asset = await upload({ type: "image", parentRef: content.ref, url: processedImageUrl, mimeType: "image/jpeg", thumbnailUrl: processedImageUrl, aiDisclosure: "app_generated" }); // Replace the image ref with the new image ref content.ref = asset.ref; } // Save the changes to the design await draft.save(); } } }); ``` ### Handling video selection ```typescript import { selection } from "@canva/design"; selection.registerOnChange({ scope: 'video', onChange: async (event) => { if (event.count > 0) { const draft = await event.read(); // Do something with the selected video ref, e.g. `draft.contents[0].ref` } } }); ``` ### Handling richtext selection ```typescript import { selection } from "@canva/design"; selection.registerOnChange({ scope: 'richtext', onChange: async (event) => { if (event.count > 0) { const draft = await event.read(); const range = draft.contents[0]; // Do something with the selected richtext, e.g. `range.readPlaintext()` } } }); ``` ## Parameters Options for configuring the content selection callback. Properties of opts}> The type of content that triggers a selection change event. **Available values**: * `"plaintext"` * `"image"` * `"video"` * `"richtext"` The callback to run when the selected content changes. **Parameters** Information about the selection change event. Properties of event}> The type of content that's selected. **Available values**: * `"plaintext"` * `"image"` * `"video"` * `"richtext"` The number of selected elements. Creates a snapshot of the selected content and returns a *draft* object. The draft has a mutable `contents` property for making changes to the selected content. Any changes made to `contents` are not immediately persisted or reflected in the user's design. To persist the changes, call the `save` method that's available via the draft. **Returns** A snapshot of content from a user's design. This is a `Promise` that resolves with the following object: The individual content items that exist within the snapshot. Any changes made to this array won't be reflected in the user's design until the `save` method is called. The text content. A unique identifier that points to an image asset in Canva's backend. A unique identifier that points to an video asset in Canva's backend. Provides methods for interacting with a range of formatted text. Formats all of the paragraphs that overlap the given bounds. * The `\n` character indicates the end of a paragraph. * All paragraphs that overlap the provided bounds will be formatted in their entirety. **Parameters** The segment of the range on which to apply the formatting. Properties of bounds}> The starting position of the segment. This is zero-based, meaning the first character of the range is at index 0. The number of characters in the segment, starting from the index. The formatting to apply to the paragraph(s). Properties of formatting}> The color of the text as a hex code. The hex code must include all six characters and be prefixed with a `#` symbol. **Example** ```ts "#ff0099" ``` The weight (thickness) of the font. The available font weights depend on the font. **Default value**: `"normal"` **Available values**: * `"normal"` * `"thin"` * `"extralight"` * `"light"` * `"medium"` * `"semibold"` * `"bold"` * `"ultrabold"` * `"heavy"` The style of the font. **Default value**: `"normal"` **Available values**: * `"normal"` * `"italic"` The decoration of the text. **Default value**: `"none"` **Available values**: * `"none"` * `"underline"` The strikethrough of the text. **Default value**: `"none"` **Available values**: * `"none"` * `"strikethrough"` An external URL that the text links to. A unique identifier that points to a font asset in Canva's backend. The size of the text, in pixels. * In the Canva editor, this number is shown as points (pts), not pixels. **Minimum**: `1` **Maximum**: `1000` The alignment of the text. **Default value**: `"start"` **Available values**: * `"start"` * `"center"` * `"end"` * `"justify"` The list indentation level of the paragraph. The appearance of list item markers. This property only has an effect if `listLevel` is greater than 0. **Default value**: `"none"` **Available values**: * `"none"` * `"disc"` * `"circle"` * `"square"` * `"decimal"` * `"lower-alpha"` * `"lower-roman"` * `"checked"` * `"unchecked"` The line height of the paragraph, relative to the font size expressed in Em. **Minimum**: `0.5` **Maximum**: `2.5` **Default value**: `1.4` The spacing between characters, relative to the font size expressed in Em. * In the Canva editor, this number is shown as a multiple of 1000. **Minimum**: `-0.2` **Maximum**: `0.8` **Default value**: `0` **Returns** `void` **Examples** **Format paragraph as a heading** ```typescript import { createRichtextRange } from "@canva/design"; const range = createRichtextRange(); range.appendText("Heading Text\nRegular paragraph text."); // Format just the first paragraph as a heading range.formatParagraph( { index: 0, length: 12 }, // Only need to include part of the paragraph { fontSize: 24, fontWeight: 'bold', textAlign: 'center' } ); ``` **Create a bulleted list** ```typescript import { createRichtextRange } from "@canva/design"; const range = createRichtextRange(); const text = "Item 1\nItem 2\nItem 3"; range.appendText(text); // Format all paragraphs as a bulleted list range.formatParagraph( { index: 0, length: text.length }, { listLevel: 1, listMarker: 'disc' } ); ``` Formats a region of text with inline formatting properties. **Parameters** The segment of the range on which to apply the formatting. Properties of bounds}> The starting position of the segment. This is zero-based, meaning the first character of the range is at index 0. The number of characters in the segment, starting from the index. The formatting to apply to the text. Properties of formatting}> The color of the text as a hex code. The hex code must include all six characters and be prefixed with a `#` symbol. **Example** ```ts "#ff0099" ``` The weight (thickness) of the font. The available font weights depend on the font. **Default value**: `"normal"` **Available values**: * `"normal"` * `"thin"` * `"extralight"` * `"light"` * `"medium"` * `"semibold"` * `"bold"` * `"ultrabold"` * `"heavy"` The style of the font. **Default value**: `"normal"` **Available values**: * `"normal"` * `"italic"` The decoration of the text. **Default value**: `"none"` **Available values**: * `"none"` * `"underline"` The strikethrough of the text. **Default value**: `"none"` **Available values**: * `"none"` * `"strikethrough"` An external URL that the text links to. **Returns** `void` **Examples** **Format specific words in a paragraph** ```typescript import { createRichtextRange } from "@canva/design"; const range = createRichtextRange(); range.appendText("This text contains important information."); // Format just the word "important" range.formatText( { index: 16, length: 9 }, { fontWeight: 'bold', color: '#FF0000' } ); ``` **Add a link to text** ```typescript import { createRichtextRange } from "@canva/design"; const range = createRichtextRange(); range.appendText("Visit our website for more information."); // Add a link to "our website" range.formatText( { index: 6, length: 11 }, { link: "https://www.example.com", decoration: 'underline', color: '#0066CC' } ); ``` Appends the specified characters to the end of the range. **Parameters** The characters to append to the richtext range. Optional formatting to apply to the appended text. Properties of formatting}> The color of the text as a hex code. The hex code must include all six characters and be prefixed with a `#` symbol. **Example** ```ts "#ff0099" ``` The weight (thickness) of the font. The available font weights depend on the font. **Default value**: `"normal"` **Available values**: * `"normal"` * `"thin"` * `"extralight"` * `"light"` * `"medium"` * `"semibold"` * `"bold"` * `"ultrabold"` * `"heavy"` The style of the font. **Default value**: `"normal"` **Available values**: * `"normal"` * `"italic"` The decoration of the text. **Default value**: `"none"` **Available values**: * `"none"` * `"underline"` The strikethrough of the text. **Default value**: `"none"` **Available values**: * `"none"` * `"strikethrough"` An external URL that the text links to. **Returns** A segment of a richtext range. Properties of bounds}> The starting position of the segment. This is zero-based, meaning the first character of the range is at index 0. The number of characters in the segment, starting from the index. **Examples** **Append plain text** ```typescript import { createRichtextRange } from "@canva/design"; const range = createRichtextRange(); range.appendText("First paragraph. "); // Append more text to the existing content const result = range.appendText("This is additional text."); // The bounds of the newly added text are returned // Do something with the bounds - result.bounds, e.g. { index: 17, length: 24 } ``` **Append formatted text** ```typescript import { createRichtextRange } from "@canva/design"; const range = createRichtextRange(); range.appendText("Normal text followed by "); // Append formatted text range.appendText("bold red text", { fontWeight: 'bold', color: '#FF0000' }); // Append a new paragraph range.appendText("\nThis is a new paragraph."); ``` Replaces a region of text with the specified characters. **Parameters** The segment of the range to replace. Properties of bounds}> The starting position of the segment. This is zero-based, meaning the first character of the range is at index 0. The number of characters in the segment, starting from the index. The replacement characters. The formatting to apply to the replaced text. Properties of formatting}> The color of the text as a hex code. The hex code must include all six characters and be prefixed with a `#` symbol. **Example** ```ts "#ff0099" ``` The weight (thickness) of the font. The available font weights depend on the font. **Default value**: `"normal"` **Available values**: * `"normal"` * `"thin"` * `"extralight"` * `"light"` * `"medium"` * `"semibold"` * `"bold"` * `"ultrabold"` * `"heavy"` The style of the font. **Default value**: `"normal"` **Available values**: * `"normal"` * `"italic"` The decoration of the text. **Default value**: `"none"` **Available values**: * `"none"` * `"underline"` The strikethrough of the text. **Default value**: `"none"` **Available values**: * `"none"` * `"strikethrough"` An external URL that the text links to. **Returns** The bounds of the replacement characters within the updated range. Properties of bounds}> The starting position of the segment. This is zero-based, meaning the first character of the range is at index 0. The number of characters in the segment, starting from the index. **Examples** **Replace text while maintaining some formatting** ```typescript import { createRichtextRange } from "@canva/design"; const range = createRichtextRange(); range.appendText("This text needs correction."); // Replace "needs correction" with "is correct" const result = range.replaceText( { index: 10, length: 16 }, "is correct" ); // The bounds of the replaced text are returned // Do something with the bounds - result.bounds, e.g. { index: 10, length: 10 } ``` **Replace text with formatted text** ```typescript import { createRichtextRange } from "@canva/design"; const range = createRichtextRange(); range.appendText("Regular text that needs emphasis."); // Replace "needs emphasis" with formatted text range.replaceText( { index: 17, length: 15 }, "is important", { fontWeight: 'bold', fontStyle: 'italic', color: '#0066CC' } ); ``` Returns the current state of the richtext as plaintext. **Returns** `string` **Examples** **Extract plain text content** ```typescript import { createRichtextRange } from "@canva/design"; const range = createRichtextRange(); range.appendText("First paragraph.\n", { fontWeight: 'bold' }); range.appendText("Second paragraph with formatting.", { color: '#FF0000' }); // Get plain text content without formatting const plainText = range.readPlaintext(); // Do something with the plain text - plainText, e.g. "First paragraph.\nSecond paragraph with formatting." ``` **Search within text content** ```typescript import { createRichtextRange } from "@canva/design"; const range = createRichtextRange(); range.appendText("This text contains a searchable term."); // Search for a specific word const plainText = range.readPlaintext(); const searchTerm = "searchable"; const index = plainText.indexOf(searchTerm); if (index !== -1) { // Format the found term range.formatText( { index, length: searchTerm.length }, { fontWeight: 'bold', decoration: 'underline' } ); } ``` Returns the current state of the richtext as one or more text regions. Each region is an object that contains the text content and its formatting. **Returns** The plaintext content of the region. The formatting of the region. Properties of formatting}> The color of the text as a hex code. The hex code must include all six characters and be prefixed with a `#` symbol. **Example** ```ts "#ff0099" ``` The weight (thickness) of the font. The available font weights depend on the font. **Default value**: `"normal"` **Available values**: * `"normal"` * `"thin"` * `"extralight"` * `"light"` * `"medium"` * `"semibold"` * `"bold"` * `"ultrabold"` * `"heavy"` The style of the font. **Default value**: `"normal"` **Available values**: * `"normal"` * `"italic"` The decoration of the text. **Default value**: `"none"` **Available values**: * `"none"` * `"underline"` The strikethrough of the text. **Default value**: `"none"` **Available values**: * `"none"` * `"strikethrough"` An external URL that the text links to. A unique identifier that points to a font asset in Canva's backend. The size of the text, in pixels. * In the Canva editor, this number is shown as points (pts), not pixels. **Minimum**: `1` **Maximum**: `1000` The alignment of the text. **Default value**: `"start"` **Available values**: * `"start"` * `"center"` * `"end"` * `"justify"` The list indentation level of the paragraph. The appearance of list item markers. This property only has an effect if `listLevel` is greater than 0. **Default value**: `"none"` **Available values**: * `"none"` * `"disc"` * `"circle"` * `"square"` * `"decimal"` * `"lower-alpha"` * `"lower-roman"` * `"checked"` * `"unchecked"` The line height of the paragraph, relative to the font size expressed in Em. **Minimum**: `0.5` **Maximum**: `2.5` **Default value**: `1.4` The spacing between characters, relative to the font size expressed in Em. * In the Canva editor, this number is shown as a multiple of 1000. **Minimum**: `-0.2` **Maximum**: `0.8` **Default value**: `0` **Examples** **Get text with formatting information** ```typescript import { createRichtextRange } from "@canva/design"; const range = createRichtextRange(); range.appendText("Normal text ", {}); range.appendText("bold text", { fontWeight: 'bold' }); range.appendText(" and ", {}); range.appendText("red text", { color: '#FF0000' }); // Get formatted regions const regions = range.readTextRegions(); // Do something with the regions, e.g. // [ // { text: "Normal text ", formatting: {} }, // { text: "bold text", formatting: { fontWeight: 'bold' } }, // { text: " and ", formatting: {} }, // { text: "red text", formatting: { color: '#FF0000' } } // ] ``` **Analyze formatting variations** ```typescript import { createRichtextRange } from "@canva/design"; const range = createRichtextRange(); range.appendText("Mixed ", {}); range.appendText("formatted ", { fontWeight: 'bold' }); range.appendText("text", { color: '#0066CC' }); // Analyze formatting variations const regions = range.readTextRegions(); const formattingTypes = regions.map(region => { const formatting = region.formatting || {}; return { text: region.text, hasWeight: !!formatting.fontWeight, hasColor: !!formatting.color }; }); ``` Saves changes made to the content items in the `contents` array. Once this method is called: * Any changes the app has made to to the content will be reflected in the user's design. * Any changes the user has made to the content since the snapshot was created may be overwritten. * Only properties that are different from the original state will be written to the design. **Returns** `Promise` **Examples** **Save changes to selected text** ```typescript import { selection } from "@canva/design"; selection.registerOnChange({ scope: 'plaintext', onChange: async (event) => { if (event.count > 0) { const draft = await event.read(); // Make changes to the content for (const content of draft.contents) { content.text = content.text.toUpperCase(); } // Save the changes to the design await draft.save(); } } }); ``` **Modify then save rich text content** ```typescript import { selection } from "@canva/design"; selection.registerOnChange({ scope: 'richtext', onChange: async (event) => { if (event.count > 0) { const draft = await event.read(); const range = draft.contents[0]; // Get the plain text const text = range.readPlaintext(); // Apply formatting to the entire text range.formatText( { index: 0, length: text.length }, { fontWeight: 'bold', color: '#0066CC' } ); // Save the formatted text back to the design await draft.save(); } } }); ``` **Examples** **Read and modify plaintext selection** ```typescript import { selection } from "@canva/design"; selection.registerOnChange({ scope: 'plaintext', onChange: async (event) => { if (event.count > 0) { // Read the content const draft = await event.read(); // Handle selected text `draft.contents[0].text` // Modify the text if needed // draft.contents[0].text = 'Modified text'; // await draft.save(); } } }); ``` **Read and analyze image selection** ```typescript import { selection } from "@canva/design"; selection.registerOnChange({ scope: 'image', onChange: async (event) => { // Check if any images are selected if (event.count === 0) { // Handle no images selected case return; } // Read the content const draft = await event.read(); // Get information about selected images const imageRefs = draft.contents.map(content => content.ref); // The ref can be used with other API methods } }); ``` **Process multiple selected items** ```typescript import { selection } from "@canva/design"; selection.registerOnChange({ scope: 'richtext', onChange: async (event) => { if (event.count > 0) { const draft = await event.read(); // Process each selected range draft.contents.forEach((range) => { // Do something with the range content and formatted regions, with `range.readPlaintext()` and `range.readTextRegions()` }); } } }); ``` **Returns** `void` ## Returns () => `void` ## Permissions To use this method, the following permissions must be enabled for the app: * `canva:design:content:read`, to register the handler and call the `read` method * `canva:design:content:write`, if the app calls the `save` method To learn more, see [Permissions](https://www.canva.dev/docs/apps/configuring-intents-permissions/). ## Rate limit This method has the following rate limits: * `read`: 100 requests every 10 seconds * `save`: 30 requests every 10 seconds