7 Min. read

Photoshop Plugins With UXP: Introduction

This is the first in what I think is going to be a series of posts about UXP — Adobe’s newest framework for writing plugins and scripts for Photoshop and other Creative Cloud apps.

This post provides a general introduction to the framework alongside some personal thoughts. Future posts will probably be shorter and cover specific development topics as I work on migrating my Expresso Exporter plugin from CEP to UXP.

A little bit of history

If you’ve ever attempted to write a Photoshop script or plugin you know that it can be quite a challenge. As far as I know of, there have been three main ways to extend Photoshop until now:

  • ExtendScript: a JavaScript-like scripting language available in Photoshop and other Adobe apps since forever. It can be used to write scripts with simple UI. It’s very old and slow and haven’t received any significant update in a long time.
  • CEP (Common Extensibility Platform): can be used to create custom panels and dialogs in Photoshop and other apps. Every CEP plugin runs in a hybrid browser (CEF) / NodeJS environment with support for fairly modern web technologies. Unfortunately, any interaction with the underlying app still needs to happen via ExtendScript, making plugins this weird monster made of different technologies.
Tech stack layers of a CEP extension, including HTML, CSS, JavaScript and ExtendScript
Tech stack of a CEP extension. Source: Adobe.
  • C++ Plugins (CSDK): native C++ plugins, also available in Photoshop since forever, are the go-to way to implement filters, file importer/exporters and generally everything that is computation-heavy. Native plugins can also be included in CEP plugins, creating so called “hybrid” plugins.

(The list above doesn’t include the Generator framework, nor the Kevlar-based API, nor CSXS — CEP’s previous iteration — for brevity.)

All these solutions are either old, slow, complex, scarcely documented or buggy and, generally, a pain to work with. There are tools — including some written by yours truly — that try to improve things, but maintaining rather complex plugins has always proven problematic.

All hail UXP

A couple of years ago Adobe presented the evolution of its extensibility platform, called UXP (Unified eXtensibility Platform). You can think of UXP as a mashup of ExtendScript and CEP. UXP supports JavaScript for scripting and HTML/CSS to create custom UI.

An overview of the relationship between UXP and each host application
UXP architecture. Source: Adobe.

UXP is under active development and it’s supported, to various degrees, in Adobe XD, Photoshop and InDesign. It looks like the long term plan is to integrate UXP in most Creative Cloud apps. Adobe seems to be taking UXP development seriously — an approach that also shows in the very well written and up-to-date documentation available online.

UXP plugins can be packaged to .ccx files and installed with a double click, making it finally easy to distribute plugins even outside of the Adobe Marketplace without requiring third-party tools or custom installers.

.ccx and a .psjs files

Scripts (using the .psjs extension) can also be run directly by double clicking the file.

Scripting

The JavaScript side of UXP is powered by the Chromium V8 engine, which means all modern JavaScript features — such as Promises, async/await, lambas, modules, destructuring, etc. — are supported out-of-the-box. WebAssembly is supported as well.

On top of JavaScript’s standard feature set, UXP exposes APIs to access the file system, as well as OS and app-specific functionality. For Photoshop in particular, work is ongoing on a DOM-like API that provides access to Photoshop documents and features (similar to the one available in ExtendScript).

const { core, app } = require("photoshop");
 
// Create a new document, add a layer to it and log its name to console
await core.executeAsModal(async () =>
{
    const myDoc = await app.createDocument();
    const myLayer = await myDoc.createLayer();
    console.log(myLayer.name);
});

Features that are not exposed via the DOM can be accessed via BatchPlay — an evolution of ExtendScript’s executeAction.

Imaging API

As a game dev, things start to get really interesting with the Imaging API. The Imaging API allows to manipulate the pixels in a Photoshop document directly. This creates many interesting possibilities to write plugins that modify images on the fly or even create new images from scratch.

const { core, imaging } = require("photoshop");
 
// Gets the content of the currently selected layer in the active document
await core.executeAsModal(async () =>
{
    const pixels = await imaging.getPixels();
    console.log(`${pixels.imageData.width}x${pixels.imageData.height}`);
 
    // We can do some processing on the pixels here, then "imaging.putPixels"
    // can be used to put the pixel data back in the layer
});

Hybrid plugins

The Imaging API can potentially be even more interesting when paired with C++ logic. UXP recently gained support for hybrid plugins containing both C++ and JS/HTML in the same package.

UXP extension and C++ logos
Source: Adobe.

This is different from what’s possible with CEP + CSDK (or even UXP + CSDK) in that the C++ code is distributed with and runs alongside the JavaScript code. Communication between the two happens via a API layer modeled against NodeJS N-API.

It should be possible to use native C++ code for performance-intensive tasks such as image processing (i.e. normalizing a normal map, inverting its channels, etc.), making it an interesting option for game dev.

(As a side note, this is what I’m currently trying out for the next iteration of Expresso.)

UI

UXP uses web technologies to create custom UI. Rendering is handled via a custom engine that promises better performance over the Chromium Embedded Framework used by CEP. Essentially, Adobe is building something that looks like a web browser, but isn’t a web browser:

Keep in mind that UXP is not a browser. It’s a platform that provides the ease of using web technologies to build plugins/scripts for desktop applications. Hence, it does not support all the HTML/CSS capabilities you can use in a browser.

On one hand, this choice makes it possible to expose features that make it easier to build plugins. Most HTML elements, for example, render in a way that’s consistent with the rest of the Photoshop UI, while CSS media queries and global variables make theme-aware styling a breeze:

/* Common styles via CSS variables */
body {
    background-color: var(--uxp-host-background-color);
    color: var(--uxp-host-text-color);
    border-color: var(--uxp-host-border-color);
    font-size: var(--uxp-host-font-size);
}
 
/* Theme aware CSS media query */
@media (prefers-color-scheme: dark) {
    body {
        /* Dark color scheme styles */
    }
}

On the other hand, this means that support for web standards in UXP isn’t that great and creates situations where CSS rules or JavaScript code simply don’t work. Adobe has been working on expanding support for web technologies over the past couple of years and seems committed to try and close the gap with web browsers (at least to an extent). We’ll see how that goes!

When it comes to UI frameworks, React is the only real safe choice for now since it’s being used internally by Adobe. Vue seems to be supported too, but I haven’t tried using it so far.

Further reading

Here’s some useful resources to get started with UXP development:

Continue to the next post in the series for an introduction about scripting with UXP.