Element Metadata
Store and retrieve custom metadata on document elements.
Overview
Add-ons can store private metadata (custom data accessible only to the add-on that set it) on elements within the Express document. There are two main approaches for working with element metadata:
- Runtime Metadata: Set and modify metadata on existing elements using the Document Sandbox APIs
- Import-Time Metadata: Attach metadata to media assets during import using the Add-on UI SDK
Runtime Element Metadata
Get and Set Element Metadata
Add-ons can store metadata on any node within the Express document. Currently, each node can hold up to 3 KB of data, organized as key/value pairs where both keys and values are Strings. Additionally, there is a limit of 20 key/value pairs per node.
All nodes that inherit from the BaseNode class have a addOnData property that can be used to store and retrieve metadata. It is an instance of the AddOnData class, which provides methods to perform operations such as getItem(), setItem(), removeItem(), and clear().
With the remainingQuota property, you can check how much space is left, both in terms of sizeInBytes and numKeys, while keys() returns an array of the keys in use.
While Document and Page metadata operate from the addOnUISdk.app.document object and belong to the Add-on UI SDK, Element metadata are part of the Document Sandbox and are accessed through the node.addOnData property.
Example
Copied to your clipboardimport { editor } from "express-document-sdk";// Create some dummy nodeconst text = editor.createText("Hello, World!");// Store some metadata as key/value pairstext.addOnData.setItem("originalText", "Hello, World!");text.addOnData.setItem("date", new Date().toISOString());// Retrieve the metadataconsole.log("Original text: ", text.addOnData.getItem("originalText"));// Check the remaining quotaconsole.log("Remaining quota: ", text.addOnData.remainingQuota);// {// "sizeInBytes": 3062,// "numKeys": 19// }// Check the keys in useconsole.log("Keys in use: ", text.addOnData.keys());// ["originalText", "date"]// Remove the metadatatext.addOnData.removeItem("originalText");// clear all metadatatext.addOnData.clear();
Please note that the addOnData property is iterable with for...of loops, so you can use it to iterate over the key/value pairs; each pair is an array with the key as the first element and the value as the second.
Copied to your clipboard// iterate over key/value pairsfor (let pair of text.addOnData) {console.log(pair);// ['originalText', 'Hello, World!']// ['date', '2025-01-20T11:06:19.051Z']}
Alternatively, you can use the keys() method to get an array of all keys and then iterate over them.
Copied to your clipboard// Iterate over all keystext.addOnData.keys().forEach((key) => {console.log(`Key: ${key}, Value: ${text.addOnData.getItem(key)}`);});
Import-Time Metadata for Media Assets (Add-on UI SDK)
When importing media assets (images, videos, animated images) using the Add-on UI SDK, you can attach metadata using the ImportAddOnData parameter. This provides two distinct types of metadata storage:
Container vs. Media Metadata
nodeAddOnData - Container-Level Metadata:
- Persists with the individual asset container
- Remains attached even when the asset content is replaced
- Each container instance has independent metadata
- Accessed via
MediaContainerNode.addOnDatain the Document Sandbox
mediaAddOnData - Content-Level Metadata:
- Tied to the actual asset content
- Shared across all copies of the same asset in the document
- Reset if the asset content is replaced with different media
- Accessed via
MediaRectangleNode.mediaAddOnDatain the Document Sandbox
Import-time metadata is not supported for PSD/AI assets. An error will be thrown if you attempt to use ImportAddOnData with these file types.
Example: Set Metadata with Add-on UI SDK ImportAddOnData
Copied to your clipboard// Store metadata when importing// ui/index.js (iframe runtime)import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js";addOnUISdk.ready.then(async () => {try {// Create or fetch your image blobconst imageBlob = await fetch("./sample-image.png").then(r => r.blob());// Import image with ImportAddOnDataawait addOnUISdk.app.document.addImage(imageBlob,// Optional MediaAttributes{title: "Sample Test Image",author: "Add-on Developer"},{// Container-level metadata (persists with container)nodeAddOnData: {"imageId": "test_001","category": "demo","importDate": new Date().toISOString(),"source": "addon-tester"},// Content-level metadata (tied to actual image content)mediaAddOnData: {"resolution": "200x150","format": "PNG","source": "generated_canvas","color": "green"}});console.log("✅ Image imported successfully with metadata!");} catch (error) {console.error("❌ Failed to import image:", error);}});
Copied to your clipboard// ui/index.js (iframe runtime)// Import a video with container metadata onlyawait addOnUISdk.app.document.addVideo(videoBlob, {title: "Product Demo"}, {nodeAddOnData: {"video-category": "product-demo","import-timestamp": new Date().toISOString()},mediaAddOnData: {"resolution": "1920x1080","format": "MP4","duration": "596s","testFlag": "remote_video_test"}});
Copied to your clipboard// ui/index.js (iframe runtime)// Import an animated image with media metadata onlyawait addOnUISdk.app.document.addAnimatedImage(gifBlob, {title: "Animated Logo"}, {mediaAddOnData: {"animation-type": "logo","frame-count": "24","duration": "2000ms"}});
ImportAddOnData is also supported in drag-and-drop operations via the enableDragToDocument method. See the Drag and Drop guide for more details.
Example: Retrieve Imported Metadata in Document Sandbox
Copied to your clipboard// sandbox/code.js (document sandbox)import { editor } from "express-document-sdk";function retrieveAllMediaMetadata() {console.log("Starting metadata retrieval...");const documentRoot = editor.documentRoot;let mediaContainerCount = 0;// Traverse document structure to find media nodesfor (const page of documentRoot.pages) {console.log(`📄 Checking page: ${page.id}`);for (const artboard of page.artboards) {console.log(`🎨 Checking artboard: ${artboard.id}`);// Use recursive traversal to find all MediaContainer nodestraverseNodeForMedia(artboard);}}function traverseNodeForMedia(node) {// Check if current node is a MediaContainerif (node.type === 'MediaContainer') {mediaContainerCount++;console.log(`\n📦 Found MediaContainer #${mediaContainerCount}: ${node.id}`);try {// Retrieve container metadata (nodeAddOnData)const containerMetadata = {};const containerKeys = node.addOnData.keys();for (const key of containerKeys) {containerMetadata[key] = node.addOnData.getItem(key);}if (containerKeys.length > 0) {console.log('📝 Container metadata (nodeAddOnData):', containerMetadata);} else {console.log('📝 No container metadata found');}// Access the media rectangle directly via the mediaRectangle propertyconst mediaRectangle = node.mediaRectangle;if (mediaRectangle) {console.log(`🖼️ Media rectangle type: ${mediaRectangle.type}`);try {// Retrieve media-specific metadata (mediaAddOnData)const mediaMetadata = {};const mediaKeys = mediaRectangle.mediaAddOnData.keys();for (const key of mediaKeys) {mediaMetadata[key] = mediaRectangle.mediaAddOnData.getItem(key);}if (mediaKeys.length > 0) {console.log('🎯 Media metadata (mediaAddOnData):', mediaMetadata);} else {console.log('🎯 No media metadata found');}} catch (error) {// Handle PSD/AI assets or other errorsconsole.log('⚠️ Cannot access mediaAddOnData (likely PSD/AI asset):', error.message);}} else {console.log('⚠️ No media rectangle found');}} catch (error) {console.error('❌ Error accessing container metadata:', error);}}// Recursively traverse all children// MediaContainers can be nested inside groups or other containersif (node.allChildren) {for (const child of node.allChildren) {traverseNodeForMedia(child);}}}console.log(`\n✅ Metadata retrieval complete! Found ${mediaContainerCount} MediaContainer(s)`);}
Copied to your clipboard// sandbox/code.js (document sandbox)// Simple access example for a known MediaContainerconst mediaContainer = /* get MediaContainerNode from document */;// Access container-level metadataconst containerMetadata = mediaContainer.addOnData;console.log("Image ID:", containerMetadata.getItem("imageId"));console.log("Category:", containerMetadata.getItem("category"));// Access media-level metadataconst mediaRectangle = mediaContainer.mediaRectangle;const mediaMetadata = mediaRectangle.mediaAddOnData;console.log("Resolution:", mediaMetadata.getItem("resolution"));console.log("Format:", mediaMetadata.getItem("format"));// Check all available keysconsole.log("Container keys:", containerMetadata.keys());console.log("Media keys:", mediaMetadata.keys());
When to Use Each Type
Use nodeAddOnData when:
- Tracking add-on-specific UI state or settings for each container
- Storing metadata that should persist even if the user replaces the media content
- Each instance of the media should have independent metadata
Use mediaAddOnData when:
- Storing information about the media content itself (source, licensing, etc.)
- The metadata should be shared across all instances of the same media
- The metadata is only relevant to the specific media content
Use Cases
Element metadata can be useful in various scenarios:
Runtime Metadata Use Cases
- Track original properties a node was created with
- Store history of subsequent changes made to elements
- Tag nodes in ways meaningful for the add-on (e.g., skip certain operations)
- Store temporary data that doesn't need to be persisted
- Maintain add-on-specific UI state for elements
Import-Time Metadata Use Cases
- Asset Attribution: Store source URLs, author information, and licensing details
- Content Management: Track asset IDs, categories, and organizational metadata
- Workflow Context: Record placement context, import timestamps, and processing flags
- Asset Relationships: Maintain connections between related media assets
- Quality Assurance: Store validation flags, approval status, and review notes
Please refer to the SDK Reference section for AddOnData for a complete list of methods, and the per-element-metadata sample add-on for a demonstrative implementation.
FAQs
Q: How do I store metadata on an element?
A: Use node.addOnData.setItem("key", "value") to store key/value pairs on any node.
Q: How do I retrieve stored metadata?
A: Use node.addOnData.getItem("key") to retrieve the value for a specific key.
Q: What are the storage limits?
A: Each node can store up to 3 KB of data with a maximum of 20 key/value pairs.
Q: How do I check remaining storage space?
A: Use node.addOnData.remainingQuota to get remaining bytes and key count.
Q: How do I remove metadata?
A: Use removeItem("key") for specific keys or clear() to remove all metadata.
Q: How do I iterate over all metadata?
A: Use for...of loops on addOnData or iterate over keys() array with forEach().
Q: Can other add-ons access my metadata?
A: No, metadata is private and only accessible to the add-on that set it.
Q: What types can I store?
A: Only strings are supported for both keys and values.
Q: How do I add metadata when importing media?
A: Use the ImportAddOnData parameter in media import methods like addImage(), addVideo(), and addAnimatedImage().
Q: What's the difference between nodeAddOnData and mediaAddOnData?
A: nodeAddOnData persists with the container even when media is replaced; mediaAddOnData is tied to the media content and shared across copies.
Q: How do I access media-specific metadata?
A: Use mediaRectangleNode.mediaAddOnData to access metadata tied to the media content itself.
Q: Can I use ImportAddOnData with all media types?
A: No, import-time metadata is not supported for PSD/AI assets. An error will be thrown if you attempt to use ImportAddOnData with these file types.
Q: What happens to mediaAddOnData when I copy a media element?
A: All copies of the same media asset share the same mediaAddOnData. Changes to one copy affect all copies.
Q: What happens to nodeAddOnData when I replace media content?
A: nodeAddOnData persists with the container even when the media content is replaced, while mediaAddOnData is reset.

