CEP to UXP Technical Migration Guide
This guide is geared towards CEP (Common Extensibility Platform) developers who would like more technical guidance on migrating their extensions to UXP (Unified Extensibility Platform). The migration process is no doubt challenging but will dramatically improve your development experience for future iterations of your plugins.
Most of this guide, including the JavaScript library and ExtendScript/DOM sections, uses Photoshop as its example host application, since that's where CEP-to-UXP migrations are most common. If you're migrating a CEP extension for a different host application, the Migrating Native CEP Functions section below covers the APIs common across host apps (file I/O, network, opening external resources); check your host's own UXP API reference for its host-specific DOM API. batchPlay is Photoshop-specific and is not available in Premiere, InDesign, or Media Encoder.
There is no clear-cut path for migration, given that CEP and UXP are fundamentally different. CEP was based on CEF (Chromium Embedded Framework), making it effectively act as a browser. UXP, on the other hand, is not a browser, and therefore complete feature parity is unlikely. This migration should serve as an opportunity to design a better, more performant version of your existing CEP extension, and perhaps a chance to add new features and support modern workflows. In fact, it is better to think of this process as a "reconstruction" with the benefits of UXP in mind, rather than a migration.
Why migrate to UXP?
UXP provides a comprehensive extensibility solution, turning JavaScript and HTML markup into controls in native application windows. You can think of UXP as a small runtime with a subset of browser functionality that we intend to grow. It has a DOM, with auto-layout and CSS Flexbox layout capabilities. UXP can run JS using the platform-specific JS engine, laying out native controls on screen, and enabling C++ and JS hybrid app development. A unified, modern execution environment (JavaScript V8) allows plugin UI and business logic to share the same context, which means no need to use evalScript anymore!
How CEP and UXP Differ
- CEP uses a full version of Chromium as a web host, which is resource intensive, especially since each CEP plugin runs in its own instance of Chromium. UXP plugins run in a single shared, lightweight engine.
- CEP doesn't talk directly to the host application. Host scripts are written in ExtendScript and passed to the app through EvalScript calls, so two different JavaScript engines are running at once. A plugin's code ends up artificially split between ExtendScript and JavaScript, and passing anything more than simple parameters between the two layers is awkward and inefficient. UXP plugins call host APIs directly from the same JavaScript context.
- CEP plugins can't use native host controls, so CEP dialogs and panels don't match host ones without a lot of CSS work. UXP plugins can use Spectrum UI components that look and feel like the host application.
- The ExtendScript side of CEP uses an old JavaScript version that lacks many modern features, so a developer has to juggle two different JavaScript environments. UXP uses a single, modern JavaScript engine throughout.
Because UXP communicates directly with the host application, the issues associated with the CEP and ExtendScript interface go away. In general, plugin development is simpler with UXP, and UXP plugins can use Spectrum CSS components for theme-aware, cross-platform user interfaces that look consistent across Creative Cloud applications.
Developer Tooling
Here is the CEP debugging guide. Here is the UXP debugging guide. The UXP Developer Tool is a plugin loader and debugger, common to every UXP host, that makes plugin management during development easier.
Plugin Types
In the CEP world, if you want the extension to appear in the Window --> Extensions menu, add the <Menu> tag. If you want the extension to launch on an event, specify those events using the <StartOn> tag. All CEP extension types must be specified in the manifest.xml file.
Meanwhile, UXP plugins are either commands or panels. Plugin metadata like the size, icons, label, and ID are defined in the manifest.json file. Plugin functionality and behavior is defined using the entrypoints.setup() API.
Commands and panels are common UXP entry point types across every host. See Panels and Commands for the full picture, including modal dialogs and combining both in one plugin.
UXP Plugin Development
Entrypoint Setup
Using the entrypoints.setup() API, handlers and menu items for the entry points are defined in the manifest. The entrypoints object consists of 3 objects:
-
plugin: This can be an object or a function. If this is a function, it is assumed as the
createhandler (described below).create(): This is called after the plugin is loaded.destroy(): This is called before the plugin is unloaded.
-
panels: This contains a list of key-value pairs where each key is a panel id (string) and the value is the data for the panel, whose type can be object/function. If the value is a function, it is assumed to be the
showmethod (described below). If an object, it can contain any of the following properties but must define eithercreateorshow. Each of the following functions returns a promise if successful and should throw an exception or return a rejected promise to signal failure.-
create(): This is called when a panel is created. -
show(): This is called when a panel is shown. -
hide(): This is called when a panel is hidden. -
destroy(): This is called when a panel is going to be destroyed. -
invokeMenu(): This is called when a panel menu item is invoked. The menu id is passed as the first argument to this function. -
menuItems: An array of menu items. Each item can be a string or an object with the properties defined below, in the same order as specified in this array. For specifying a separator, a value of "-" or a menu item with label "-" can be used at the required place in the array.{string}id - identifier of the menu item.{string}label - display text for the menu item. Should be localized. If label is not specified, id is used as label.{boolean}enabled - enabled/disabled state for the menu item. Default - true.{boolean}checked - checked state for the menu item. Default - false.{Array}submenu - submenu for this menu item, again as an array ofmenuItems. Theidof submenus should still be unique across the panel.
-
-
commands: This object contains a list of key-value pairs where each key is the command ID and each value is the command's data, whose type can be an object or function. If the command's data is a function, it is assumed to be the
run()method.run(): This is called when the command is invoked via a menu entry.cancel(): This is called when the command is cancelled/aborted before completion.
User Interface
UXP plugins can use platform-native HTML and CSS components such as buttons and input fields. A plugin can also use Spectrum UXP components.
Migrating Native CEP Functions
There is a common set of APIs that plugins need access to, regardless of the host app. These include file I/O and network, among others. This section discusses the native CEP APIs extensions rely on, and guidelines for migrating those APIs to UXP.
Directory and File I/O Operations (File APIs)
In CEP extensions, developers use the window.cep.fs object to perform directory and file I/O operations. CEP developers have arbitrary access to disk and machine resources, and arbitrary ability to launch processes (including bash scripts). Users do not have to consent to extensions accessing their file system. Because developers have full access, all file access is technically persistent. Secure storage, on the other hand, does not exist for CEP.
UXP's storage module can read and write files and folders in the user's file system, but with different API signatures. Because of the sandboxing requirements of recent OS releases, UXP does not allow arbitrary access to any file on the host system. Therefore, these files are accessed by making a request to the user (by showing a file-picker dialog) and obtaining a token. File accesses outside of the plugin's root folder, the plugin's data folder, and a plugin temporary folder require the user's permission. Persistent file storage and secure storage are available in UXP as well. Refer to these notes: file access, persistent file storage, and UXP-specific (secure) storage. If you need constant access to a location to write and read files, for now, use the UXP-sanctioned location for your plugin data or persistent file access tokens.
Encoding API
CEP extensions can access the encoding API with the window.cep.encoding object. With this API, they can implement encoding while reading and writing file content. CEP developers can use UTF8 or Base64 encoding.
UXP's encoding options depend on which file API you use. The storage module's File.read()/write() take a format Symbol, formats.utf8 or formats.binary. The Node.js-style fs module's readFile()/writeFile() instead take an encoding string literal: "utf-8", "utf-16be", or "utf-16le".
Opening Remote Resources
The window.cep.util object gives CEP developers access to openURLInDefaultBrowser, registerExtensionUnloadCallback, and storeProxyCredentials. In UXP, the "shell" module contains an equivalent for openURLInDefaultBrowser (opens a web link in the system browser): openExternal. The UXP shell module does not (and will not) have equivalent methods for registerExtensionUnloadCallback and storeProxyCredentials in CEP.
In UXP, you can use openPath and openExternal to open external files and remote resources. With UXP 6.0.2+, manifest v5 asks that you define which URI schemes and file extensions your plugin is permitted to launch. This permission is common to every UXP host.
Process APIs
CEP developers can use the window.cep.process object to create (and maintain) processes. Here is the code to create a process and check if it is running:
var result = window.cep.process.createProcess("usr/X11/bin/xterm");
if (result.err === 0) {
var pid = result.data;
result = window.cep.process.isRunning(pid);
if (result.data === true) {
// running
}
}
UXP does not yet have its own comprehensive set of process APIs. In UXP, you can use openPath and openExternal to open external files and remote resources. With UXP 6.0.2+, manifest v5 asks that you define which URI schemes and file extensions your plugin is permitted to launch.
"requiredPermissions": {
"launchProcess": {
// allows launching files with specified URI schemes
"schemes":
[ "https", "slack", "adobe" ],
// allows opening files with the specified file extensions
"extensions":
[ ".pdf", ".xd", ".psd" ]
}
}
Both openPath and openExternal rely on this permission set, and upon either function call, the user will get a runtime consent dialog. Only after they agree will the API call be executed.
Migrating CEP JavaScript Libraries
CEP JavaScript Libraries are counterparts of the Flex CSXS Library. They provide JavaScript APIs to access host application and CEP information. UXP allows each host application to expose APIs directly to the developer. This section looks specifically at what the Photoshop UXP API can provide to help you migrate.
Vulcan Interface: Communicating across plugins within Photoshop
Vulcan is an (older) Adobe library that serves as an inter-plugin communication mechanism across Adobe host applications. These APIs are not accessible from within UXP, but inter-plugin communication APIs have been added in UXP 6.0.2.
In UXP 6.0.2 (manifest v5), you can specify the appropriate flags in the manifest and use invokeCommand to execute both commands and showPanel to expose panels from other plugins.
CS APIs: Communicating with your plugin and with Photoshop
CSInterface is used to access information about the host application in which an extension is running, launch an extension, register interest in event notifications, and dispatch events. CSInterface helps with theming in Photoshop as well as customizing menus (fly-out and context menu).
Use Case: API Version (Photoshop)
This section applies to Photoshop. API versioning and modal execution differ by host; consult your target host's manifest and API documentation.
In CEP, you need to check the version tag of the CEP JavaScript APIs against the version of CEP integrated by Photoshop to make sure the API you want to use is available. CSInterface.getCurrentAPIVersion() is a method that retrieves the version of CEP integrated by Photoshop.
In UXP, the Photoshop version that a plugin is targeting needs to be specified in the manifest configuration. Additionally, you can specify the Photoshop API version the plugin should target.
The v1 implementation (Photoshop 2021) does not allow plugins to run in isolation, thereby allowing any plugin to modify the Photoshop state at any time. Photoshop 2022 introduced a modal JS scope, allowing plugins to run in isolation and guaranteeing against plugins modifying Photoshop at the same time.
What changes were made for v2?
- Suspend/resume history states. See executeAsModal for details.
- Updated Photoshop DOM access. See the changelog.
Photoshop 2021 supports only v1, while Photoshop 2022 supports both v1 and v2 of the API. Developers can specify which version to target in the manifest. The manifest has a host value that takes a data object with an apiLevel field. This apiLevel field is optional and by default:
- A plugin on a PS version less than 23.0.0 will target Photoshop API v1
- A plugin on a PS version of 23.0.0 or greater will target Photoshop API v2
While plugins can still use v1, many new features are only available on Photoshop API v2 and support for v1 will be removed in a future major update to Photoshop. Only special use cases that rely on either the Photoshop menu state, or other non-modal user interactions while the plugin is running, may need to stick with v1.
The following three calls are common UXP APIs available on every host, independent of the Photoshop-specific apiLevel discussion above:
Access host application version: require("uxp").host.version
Access UXP API version: require("uxp").versions.uxp
Access plugin version from manifest: require("uxp").versions.plugin
Use Case: Sending/Receiving Events
CSXS/CEP events are used to send events among extensions in an application, and among extensions in different applications.
Host DOM events: Each host application exposes its own DOM events and notification APIs. Consult your target host's UXP API reference for the events it supports.
UXP lifecycle events: UXP lifecycle events help manage a plugin's lifecycle. Host implementations can have limitations, so verify lifecycle behavior in your target host's documentation.
The following are plugin lifecycle events your plugin can listen for:
uxpcreateplugin: Indicates that the plugin's JS context has been created and that the plugin has been loaded successfully.uxpdestroyplugin: Indicates that the plugin's JS context is about to be destroyed.
The following are panel lifecycle events your plugin can listen for:
uxpcreatepanel: Indicates that a panel is about to be created. This is called when a panel is first displayed, and each time after it is destroyed.uxpdestroypanel: Indicates that a panel is about to be destroyed.uxpshowpanel: Indicates that a panel is about to be shown and should render its UI into the attached node. This is called every time a panel is shown.uxphidepanel: Indicates that a panel is hidden.uxpinvokepanelmenu: Indicates that a panel menu item has been invoked.
The following are command lifecycle events your plugin can listen for:
uxpcommand: Indicates that the user (or Photoshop) has requested the plugin execute a command.uxpcommandrun: Indicates that the plugin will execute a command.uxpcommandcancel: Indicates that the user has cancelled the command.
To support these events, we've added:
- Async support on entrypoints.
- Entrypoints parameter changes with the latest manifest update: v4 takes
{event name, node, PanelID, PanelInfo}; v5 takes node and data (if applicable). - Entrypoints now have a default timeout of 300 milliseconds.
Use Case: Network Access
Networking is a common UXP capability available on every host. CEP used to leverage CSInterface.js to get network information. Network access in UXP requires you to define the domains the plugin will access in the manifest. You can do this by adding the network object to the requiredPermissions section of the manifest.
UXP supports XMLHttpRequest, WebSockets, and Fetch APIs to perform network requests.
Limitations:
- On macOS, it is not possible to use self-signed certificates with secure WebSockets.
- WebSockets do not support extensions.
Use Case: Customizing Menus
CSInterface has two APIs that can be used for customizing flyout menus. Refer to these notes for an example of how to configure content for flyouts, and how to use them in tandem with event listeners.
UXP support and limitations for menus vary by host. Where supported, flyout menus are defined by a JSON structure passed to the entrypoints.setup method. When a user invokes a menu item, UXP passes its menu ID to the plugin handler. Define labels for the menu items users select in the manifest.json file (EntryPointDefinition.label). See the Photoshop flyout-menu example for that host's implementation.
Use Case: Theme Support
CSInterface uses the theme manager interface and CSInterface to update the extension theme after the host application's theme changes. CSInterface does this by listening for the CSXS event com.adobe.csxs.events.ThemeColorChanged. CEP would access the latest host theme information using var skinInfo = JSON.parse(window.__adobe_cep__.getHostEnvironment()).appSkinInfo;. This skinInfo object holds the host theme info and font info and can be modified. In UXP, you can add theme awareness using Spectrum CSS and media queries; verify the target host's UI component and theme support.
Use Case: Localization
CEP uses host environment information provided in the manifest to load and update the extension. This guide details CEP support for localization. UXP localization support varies by host. In Photoshop, you can retrieve locale information for the host environment and localize plugin menu and panel labels in the manifest. Consult your target host's documentation for its localization support.
Use Case: Keyboard Events
CEP allows you to register an interest in specific keyboard events to prevent them from being sent directly to the host application, allowing you to implement your own callback functions. UXP handles keyboard events and shortcuts differently by host. In Photoshop, a plugin can set focus on a control inside a panel and listen for several types of keyboard presses, but it cannot globally override host shortcuts. See the Photoshop forum discussion for that host; consult your target host's documentation for its limitations.
Use Case: Adjusting Plugin Size
For modal and modeless CEP extensions, using window.__adobe_cep__.resizeContent() takes two parameters (width and height) and resizes the extension's content to the specified dimensions. The width and height parameters are expected to be unsigned integers. Extension min/max size constraints as specified in the manifest file apply and take precedence. If the specified size is out of the min/max size range, the min or max bounds will be used.
UXP plugins are restricted to the sizes defined by the manifest (minimumSize, maximumSize, preferredDockedSize, preferredFloatingSize) and cannot control the size of their panel programmatically — this manifest-based sizing model is common across UXP hosts and is the deliberate replacement for CEP's window.__adobe_cep__.resizeContent(). The manifest fields are identical everywhere, but each host renders and docks panels using its own UI shell, so exact visual behavior and minimum practical sizes can differ by host. The expectation is that plugin developers will design their UI in a responsive manner, allowing the user to configure the panel to their liking. If your plugin consists of multiple accordions in the UI, you might want to consider shipping with multiple panels instead. Then the user can group all those panels together, resize them, reorder them, collapse them, etc., as defined in the manifest.
Migrating ExtendScript/EvalScript to the Photoshop DOM API
This section applies to Photoshop. batchPlay and the executeAction mapping described here are not available in Premiere, InDesign, or Media Encoder.
JSX files define functions and objects to be executed in Photoshop's ExtendScript environment. These are executed in CEP either at plugin load time or using evalScript. You specify the path to JSX files in the <ScriptPath> node in manifest.xml.
All ExtendScript calls to Photoshop were synchronous and blocked the host application UI while executing. In UXP, method calls are asynchronous and do not block the UI thread — await them, or chain them with .then(), to get their result. Some UXP API members, like property getters and setters, are exposed synchronously and don't return Promises, so they don't need to be awaited.
In UXP, you cannot load and execute JSX files directly. Instead, you can access the Photoshop DOM directly using the Photoshop DOM API. If the current implementation does not fulfill your needs, you can use batchPlay to execute Photoshop actions.
batchPlay is the evolution of executeAction from ExtendScript. Where executeAction could only play one descriptor at a time, batchPlay accepts an array of action descriptors. In ExtendScript, we provided a class around constructing descriptors, references, and putting values in. With batchPlay, we have replaced these related classes with actionJSON. If you have used executeAction in ExtendScript, you may recall 4-character codes (OSTypes) and helper methods around them. In actionJSON, we instead use extended string identifiers such as colorSampler. You can still use an OSType by pre-pending it with a $ sign and passing that as a string, like $app.