Experience League | Image

Create an AEM Custom Metadata panel in Creative Cloud apps in 90 minutes!

Want to make a desktop version of your Adobe Experience Manager metadata schemas for your Creative Cloud users? Now you can, with Adobe’s Common Extensibility Platform. CEP is an HTML and NodeJS based extensibility platform that’s both powerful and easy to use. CEP extensions can interact with documents and web services, enabling robust enterprise use cases for creative users. Unlike C++ extensions, CEP extensions are cross-platform and cross-version, making maintenance a breeze. You can also deploy CEP extensions using the CC Packager or command-line tools.

Table of Contents

Lab Overview

This lab will provide you with an introduction to the structure of a CEP extension. At the conclusion of the lab, you will have a fully functional extension that works in Adobe Bridge. This extension will allow the end user to both read and change specific metadata on an asset, some of which will have been applied by AEM Assets. You will also understand how to code against the XMP libraries that underpin metadata in Creative Cloud desktop applications and AEM.

Key Takeaways

  • Discover resources and examples for working with CEP
  • Learn best practices for building CC extensions with CEP
  • Build and deploy a custom metadata panel


Working knowledge of:

  • Adobe Bridge 2018/2019
  • Text Editor (VSCode)
  • Google Chrome Browser


  • Adobe ExtendScript Toolkit

Lesson 1 - Get started with CEP


  1. Review basic CEP folders structure and required files.
  2. Complete Manifest.xml
    • Modify ExtensionID, HostList and DispatchInfo

Lesson Context

We will go over folders structure of a CEP project and the CEP extension manifest.xml.

Exercise 1

  1. Open Manifest.xml

  2. Give your CEP Extension a unique identifier by modifying ExtensionBundleId.

  3. We must specify a list of available panel(s) by their extension ID in the ExtensionList section.

  4. Next, we define supported Adobe application and its version for this CEP extension. Each desktop application is a "host," and each has its own code and version targets. For our lab, we enable this extension on Adobe Bridge 2018 and up.

  5. You define the required CEP runtime version for your target host application(s). For example, CEP Runtime version 9.0 can be run on Creative Cloud 2019 desktop apps.

  6. Finally, we want to provide the manifest with all the panel's detail that is included within this extension. DispatchInfoList consists of individual panel information such as panel entry point, visibility, UI geometry, and etc. Modify the following:

    • <MainPath> = Entry Point for HTML
    • <ScriptPath> = Path to JSX to be loaded at panel launch

Lesson 2 - JSX Startup Script


  1. Review Bridge_SelectionEvent JSX script.
  2. Create Event Listener in CEP to receive dispatched event from JSX.
  3. Enable startup script in Adobe Bridge.

Lesson Context

In order to communicate with the Bridge application, we must enable a startup JSX script. This script will notify our CEP panel when the thumbnail selection event is detected within Adobe Bridge.

Exercise 2

  1. Open Adobe Bridge 2019 -> Preferences -> Startup Scripts

  2. Click Reveal My Startup Script

  3. Copy "Bridge_SelectionEvent" in ../host/startup to the startup script folder

  4. Quit Adobe Bridge

  5. Open ../client/js/index.js in text editor

  6. Create an event listener to detect dispatched event from JSX. We will use the CSInterface library to add an EventListener with event id "cep.extendscript.event.selectedEvent" when index.html loaded.

    • Go to the following line $(document).ready(() => {...} and add the following code
  7. Open Adobe Bridge and Launch CEP Extension to test thumbnail selection event.

    • Windows -> Extensions -> Summit 2019 CEP Lab
  8. Select a thumbnail in Adobe Bridge Content window to verify event selection startup script.

Lesson 3 - XMP SDK in JSX


  1. Complete XMPAdapter Class in XMP.jsx
    • Import XMP SDK Library
    • Complete XMPAdapter methods
      • Complete TODO items in XMPAdapter.prototype.open
      • Complete TODO items in XMPAdapter.prototype.get
      • Review getArrayItems = function(namespace, property){...}
      • Review getStructObj = function(namespace, property){...}
      • Complete TODO items in XMPAdapter.prototype.set
      • Complete TODO items in XMPAdapter.prototype.commit
      • Complete TODO items in XMPAdapter.prototype.close

Lesson Context

We will explore XMP Toolkit SDK to retrieve and modify XMP MetaData.

Exercise 3

  1. In Text Editor, open ../host/XMP.jsx

  2. We need to load AdobeXMPScript library by instantiating the ExternalObject class.

  3. XMPAdapter.prototype.open

    • First, we must open the file by instantiating XMPFile object. This class corresponds to the Adobe XMP Toolkit’s File Handler component, which provides convenient I/O access to the main, or document level, XMP for a file. For getXMP(...) function, we want to open a file for modify access.

    • Once we open a file to retrieve XMP MetaData, we must call getXMP() method from XMPFile object. This method returns an XMPMeta object. XMPMeta class provides the ability to create and query metadata properties from an XMP namespace. The class also provides static functions that allow you to create and query namespaces and aliases.

  4. XMPAdapter.prototype.get

  5. getArrayItems = function(namespace, property){...}

  6. getStructObj = function(namespace, property){...}

  7. XMPAdapter.prototype.set

    • set(...) method is a sequence of logic that help set a value of a XMP property. If you have a custom namespace or a namespace that not registered in the file XMP Metadata, a namespace must be registered before continuing.
    • If a value is an Array, we must either delete the current property or append a value to it. For the simplicity, we overwrite the existing array property with a new array.
      • To overwrite, we delete the existing property using deleteProperty() method in XMPMeta object.
      • Next, we create an empty property with a type of Array using setProperty(...) method.
      • Finally, we loop through the value and utilize appendArrayItem(...) method to append each item to the array.
    • If a value is a string then we can pass a value to setProperty(...) method.
  8. XMPAdapter.prototype.commit

    • Since we know how to write XMP, we must commit the change that we made. To commit the change, we must call putXMP(...) from XMPFile object and pass modified xmpMeta back in.
    • Note: putXMP(...) does not write the modified metadata back into the file. We must close the opened XMPFile to finalize the change.
  9. XMPAdapter.prototype.close

    • Close an opened file by calling closeFile() from XMPFile object.

Lesson 4 - Bridge the gap between CEP & JSX


  1. Create functions in JSX for get/set XMP from CEP
  2. Call JSX get/set XMP functions from CEP
  3. Review code on how to display retrieved XMP Metadata from JSX.

Lesson Context

We will look at how to get/set XMP metadata from our custom XMPAdapter to CEP.

Exercise 4

  1. Open ../host/XMP.jsx

  2. In the previous lab, we created XMPAdapter class to simplify the way we retrieve XMP data. In this step, we will need a way for CEP to be able to retrieve XMP metadata.

    • First, we need to pass an object from CEP to XMPCEPHelper.getXMP(...) function.
      • The object for the parameter should contain the current filename, namespace URL, and property name
    • Next, we create new XMPAdapter object, open the file, and retrieve property value within the specified property in a namespace.
    • If there is a value, we need to return JSON.stringify of the result.
  3. We do a similar method for setting XMP data from CEP.

    • To set XMP, we require additional information such as a prefix and a value.
    • Create new XMPAdapter object, open the current file and call set method.
  4. Lets call XMPCEPHelper.getXMP(...) in JSX from CEP.

    • By invoking getXMPfromJSX(...) function, we need to pass an object that contains the following keys
      • filename
      • namespace
      • property
    • Next, we serialize this object for JSX by JSON.stringify the paramObj.
    • Using our helper function JSXHelper.runEvalScript(...), we will evaluate XMPCEPHelper.getXMP(paramObj) in JSX
  5. Lets call XMPCEPHelper.setXMP(...) in JSX from CEP.

    • By invoking setXMPfromJSX(...) function, we need to pass an object that contains the following keys
      • filename
      • namespace
      • prefix
      • property
      • value
    • Next, we serialize this object for JSX by JSON.stringify the paramObj.
    • use helper function JSXHelper.runEvalScript(...) to evaluate XMPCEPHelper.setXMP(paramObj) in JSX

Lesson 5 - Display and Edit XMP Metadata


  1. Review Metadata Display

Lesson Context

We will review how to display the XMP Metadata to the panel.

Exercise 5

  1. Open ../client/index.jsx

  2. In lesson 2, we added event handling to listen for a selection event by JSX startup script. When the CEP extension received the dispatched event, it will need to retrieve and populate XMP data to our UI by calling populateXMPFields() function.

  3. In populateXMPFields() function, We will begin populating the editable fields (Name, Cat Breed, Age, and Gender).

    • First, we created a list that map the form elements to XMP property.
    • Next, To save ourself from writing repetitive code, we will simply iterate through each of the items in the list. We are going to call getXMPfromJSX(...) and pass the corresponding XMP namespace and the property name.
  4. For our extension, we wanted to populate geotagging information by pulling GPS data from the EXIF namespace to a map.

    • EXIF stored GPS coordinate in Degrees Decimal Minutes (DDM) format and we need to convert it to Decimal Degrees (DD)format by calling our MapHelper.convertDDMtoDD(...) function (See ./client/js/utils.js).

    • Once we have the correct format for the coordinate, we can use Leaflet library to draw the map and pin.

  5. Lastly, we will be displaying top 10 DAM PredictedTag generated by AEM.

  6. Since we made a couple of fields editable, we want the end user to press Save to call the save() function.

    • Similar to retrieve Name, Cat Breed, Age and Gender fields in step 3, we will call setXMPfromJSX(...) and pass it the corresponding XMP namespace, property name, prefix and field value.

Lesson 6 - CEP Debugging


  1. Enable Debug mode and Allow unsigned extension
  2. Create .debug file
  3. Using Google Chrome Browser - Developer Tools to debug CEP.

Lesson Context

We will go through the process of debugging CEP extension with Google Chrome Developer Tool.

Exercise 6

  1. By default, debug mode and allowing unsigned extension is disabled. To enable we must do the following.

    • macOS

      • Open Terminal and input the following command: defaults write com.adobe.CSXS.9 PlayerDebugMode 1
      • Reboot your machine.
    • Windows

      • Open Registry Edit and Navigate to the following location HKEY_CURRENT_USER/Software/Adobe/CSXS.9
      • If you do not see CSXS.9 folder, you can create a new one.
      • Add the following String type key
        • PlayerDebugMode with a value of 1
  2. Create a new file called .debug in the root of CEP extension

    • Extension Id must match the extension Id that we input in Lesson 1.3.
      • If the extension have multiple panel then you must specify each of the extension id here
    • HostList is a list of supported application for your panel. Each Host Name have a corresponding port that will be use for debugging.
  3. To debug, we need to launch the Adobe Bridge 2019 and our CEP panel then with Google Chrome browser navigate to the following URL: http://localhost:8008

  4. To add a debug breakpoint use debugger keyword and place it within your javascript to tell the debugger where to stop. This will allow you to step through the code.

Useful Resources

Getting Started with CEP Extensions

CEP Debugging

CEP Package, Distribute, Install

Adobe Bridge CEP Cookbook

Using ES6 promises to write async “evalScript()” calls

Scripting Access to XMP Metadata

XMP Developer Center