UXP has updated the Manifest to version 5. Weâll see whatâs new, whatâs changed, how to deal with your pluginsâ code and users. Iâm going to summarize the most salient/tricky points, and link the relevant documentation for you to dig deeper.
Prerequisites and the CC Marketplace
Manifest v5 is officially supported since Photoshop 2022 (version 23.3.0), that sports UXP v6.0.2. Developers are allowed to submit Manifest v5 based plugins in the CC Marketplace, and users can install them provided that theyâve got Creative Cloud Desktop v5.7.
What happens when you push an updated version of your UXP plugin with Manifest v5 to the CC Marketplace? It depends on what users currently have installed in their system.
- Photoshop v23.3: theyâll be able to acquire/update your plugin and use the latest one (with Manifest v5).
- Photoshop older than v23.3 and an older (Manifest v4) version of your plugin: they must update Photoshop first, then theyâll be able to install the new plugin version.
- Photoshop older than v23.3, and theyâve not acquired your plugin yet: they will not be offered the older (Manifest v4) plugin. They must update Photoshop and get the newest (Manifest v5) plugin.
In other words, the CC Marketplace doesnât work like the Apple Store, where youâre deployed the latest product version that is compatible with your system. Here, only the latest product is made available, and users must keep up with the minimum requirements if they want to get it.
Photoshop API v2
With the Manifest v5 you can unlock all the new Photoshop Scripting API features currently available; theyâre documented in the Adobe Developer website (see the changelog).
Theyâre not on by default, though: you must explicitly declare to use the Photoshop API v2 in the manifest.json
.
"manifestVersion": 5,
"host": [
{
"app": "PS",
"minVersion": "23.3.0",
"data": {
"apiVersion": 2
}
}
],
Few things to note:
"host"
is now an array of objects: in fact Manifest v5 supports multi-host UXP plugins (at the moment Photoshop and Adobe XD at best).- Thereâs a new
"data"
property, where you can specify the"apiVersion"
: set it to2
. - As expected,
"manifestVersion"
is now5
, and please note the"minVersion"
that must be at least"23.3.0"
.
You can still set "apiVersion"
to 1
if you want, but youâll get a warning message in the Console and, most importantly, youâll miss all the new PS Scripting features1.
Modal State
Speaking of Photoshop Scripting, among all the DOM additions the one API v2 feature that is going to have the biggest impact (and by far) on your existing code is the new requirement to put Photoshop in a Modal State when some operations are performed. Quoting the official documentation:
(it) is needed when a plugin wants to make modifications to the Photoshop state. This includes scenarios where the plugin creates or modify documents, or the plugin wishes to update UI or preference state.
When Photoshop is in a modal state, some menus are disabled and the script steals the host applicationâs attention, so to speak; very little interaction is then allowed.
For which operations is the modal state now required? An annoyingly lot of them, youâd be tempted to instinctively reply, e.g. when:
- Dealing with File I/O (e.g. saving a document)
- Setting some properties via BatchPlay (e.g. History states)
- Acting on layers (e.g. running Filters or Adjustments)
- Anything else that âmay modify the state of Photoshopâ.
What in API v1 used to work out-of-the-box, may now throw "Uncaught Error: Event: <whatever> may modify the state of Photoshop. Such events are only allowed from inside a modal scope"
.
How do you create such modal scope then? With the mighty executeAsModal()
function, with which youâre going to wrap all offending calls. This is a very basic example:
const ps = require('photoshop');
const bp = ps.action.batchPlay;
const executeAsModal = ps.core.executeAsModal;
await executeAsModal(async () => {
// In the modal state now
await ps.app.activeDocument.duplicate();
await bp([{ _obj: "invert" }], {}); // Image > Adjustments > Invert
}, {commandName: "Modifying the state of PS like there's no tomorrow"})
As a bonus, if the code youâre running takes more than 2 seconds to complete, a progress bar pops up by default.
In fact, the whole rationale is to give users a way to interrupt tasks if/when needed. The functionâs signature is:
executeAsModal(targetFunction, options)
In my example Iâve used an anonymous (and asynchrohous) targetFunction
inline; executeAsModal()
itself returns a promise, hence you may want to use await
2 in front of it.
The targetFunction
in turn receives two objects as parameters (that I wonât mention here for simplicityâs sake), while in the options
object the only required property is commandName
, that defines the string that appears in the progress bar popup.
I strongly encourage you to read the whole content of its documentation page, as executeAsModal()
âwhen you get over the initial annoyanceâis extremely powerful. I wonât cover every feature in detail here, but let me mention that among the rest you can use it to:
- Suspend and resume the History multiple times.
- Update both the progress bar and the displayed text.
- Register documents to be automatically closed if the user cancels the process (how cool is that).
If youâre wondering, API v2 also include suspendHistory()
(see the documentation) which is a simpler wrapper for executeAsModal()
.
Permissions
The Manifest v5 introduces a new "requiredPermissions"
property, that may look something like this:
"requiredPermissions": {
"launchProcess": {
"schemes": ["https", "mailto", "file"],
"extensions": [".pdf"]
},
"network": { "domains": ["https://cc-extensions.com"] },
"allowCodeGenerationFromStrings": true,
"localFileSystem": "plugin",
"clipboard": "read"
}
The best documentation for each one of the properties at the moment of this writing is found in this article by Padma Krishnamoorthy; Iâll leave it to her for the technical details, let me cover the bird-eye view here.
In case you havenât noticed yet, the UXP team takes the concepts of Usersâ trust and consent quite seriously. In this spirit, in the Manifest youâre going to declare upfront what kind of permissions your plugin needs, e.g. to open a website, read the clipboard, mess with the Filesystem, etc. Eventually, this information is going to be displayed in your pluginâs CC Marketplace product pageânot yet, but they plan toâin order to give users full disclosure about the ways your plugin will interact with their system. I suggest to keep extra permissions at minimum, and include only the ones you really need.
Be also aware that, in addition to the permissions that you have to generically declare in the Manifest (I want to access "https"
URLs, I need to open ".pdf"
files), a âRequest for Permissionâ popup will open anyway when you reach out for that specific https address, or that specific pdf file.
This way users can grant access to, and are exactly informed about, the exact resource your plugin is dealing with.
Changes in Entrypoints
Finally, thereâs one little detail in the Manifest v5 transition that is able to crash your plugin, and it boils down to the different parameter received by the show()
function in the panelâs entrypoints
.
const entrypoints = require('uxp').entrypoints
entrypoints.setup({
panels: {
yourpanel: {
menuItems: [ /*...*/ ]
invokeMenu(id) {/*...*/},
create() { /*...*/ },
destroy(){ /*...*/ },
show(payload){ /*...*/ }, // <== this guy here
hide(){ /*...*/ }
}
}
})
In Manifest v4 plugins, show()
used to receive an object with a node
property: the HTML <body>
that very likely youâve always used to .appendChild()
a <div>
to.
// Manifest v4
var root;
create() {
root = document.createElement("div");
// etc...
}
show(payload) {
payload.node.appendChild(root); // <== <body> is the payload.node
}
In Manifest v5 UXP plugins thereâs no need to access the node
property, the <body>
is received directly:
// Manifest v5
show(payload) {
payload.appendChild(root); // <== <body> is the payload
}
As far as I know this has still to be fixed e.g. in the UXP Developer Toolâs React template (in which case, find line 44 and substitute event.node
with event
and itâll work back as normal).
Conclusions
git checkout -b manifest5
and be happy đ.
Thanks for following along! If you find this content useful, please consider supporting me: you can either purchase my latest UXP Course with ReactJS, or donate what you can like the following fine people didâitâll be much appreciated! đđť
Thanks to: John Stevenson âď¸, Adam Plouff, Dana Frenklach, Dmitry Egorov, Roberto Sabatini, Carlo Diamanti, Wending Dai, Pedro Marques, Anthony Kuyper, Gabriel Correia, Ben Wright, CtrlSoftware, Maiane Araujo, MihĂĄly DĂĄvid Paseczki, Caspar Shelley.
Stay safe, get the Nth vaccine shot if/when you can â bye!
The whole series so far
- #01 â Rundown on the UXP announcement @ the Adobe MAX 2020
- #02 - Documentation
- #03 - UXP Developer Tool
- #04 - Commands vs. Panels and the manifest.json
- #05 - Sync vs. Async code in Photoshop DOM Scripting
- #06 - BatchPlay (part 1): the ActionManager roots
- #07 - BatchPlay (part 2): Alchemist as a UXP Script Listener
- #08 - BatchPlay (part 3): Alchemist as a UXP Inspector
- #09 - Adobe Spectrum UXP
- #10 - Modal Dialogs
- #11 - Flyout Menus and Entrypoints
- #12 - React JS and the UXP plugins Course
- #13 - Manifest v5
- #special - The UXP Landscape Guide