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.addOnData
in 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.mediaAddOnData
in 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.