The goal of this article is to be a small window into the world of creating custom Photoshop extensions. After reading this post, I hope you will get enough knowledge to bend Photoshop to your will to achieve something which is outside of the default behaviour of an Adobe product.

This article is split into two main parts. In the first part, you will learn all the basic stuff that is required for you to start working on a new custom extension. In the second part, you will get more advanced knowledge such as debugging and integrating custom extension with Adobe Tools API.

Prerequisites

Adobe Photoshop CC installed and basic knowledge of HTML, CSS, and JavaScript.

Part 1

Technical overview

To create an extension for Adobe applications such as Photoshop you need to use CEP. CEP stands for Common Extensibility Platform and it's a framework that unified the way in which developers can create custom extensions for most Adobe tools available. At the time of writing of this article, CEP supports 13 of them - Photoshop, InDesign, InCopy, Illustrator, Premiere Pro, Prelude, After Effects, Animate (Flash Pro), Audition, Dreamweaver, Muse, Bridge, Rush.

Since CEP 5 which was released in 2014, CEP was totally rebuilt to support web technologies. It means that you can build UI for extensions in HTML and style it in CSS. And of course, all logic that you need can be written in JavaScript and Node.js. It is because each panel uses an embedded instance of Chromium (CEF) which is no different from a standard desktop browser. Thanks to that you can use all custom JS frameworks such as React, Vue, and so forth, to create dynamic UI for our extension. The only difference between an instance of Chromium embedded in a panel and a normal browser is the version of it. So, if you are using a modern JavaScript framework it is important to use Babel. To make things easier, each panel has access to Node.js even if the user does not have it installed locally on his machine.

At the time of writing this article, CEP 9 is the newest version and it has Chromium version 61.0.3163.91 and Node.js version 8.6.0.

Extension code does not have access to the low-level API of host tools such as Photoshop. All scripts which need to access tool API are written in ExtendScript files. ExtendScript is a special scripting language created by Adobe to extend all of its applications. It implements the dialect of ECMAScript 3 standard therefore it is very easy to learn to someone with even basic JavaScript knowledge. ExtendScript uses the .jsx extension for scripts.

Communication between an extension and an Adobe Tool is handled by a special JavaScript library created by Adobe, called CSInterface. This library is responsible for direct communication between extensions and host tools. A more detailed description will be provided in the later section.

Getting Started

Tools

Since you are going to write mainly HTML, JavaScript and CSS, you don't need any special editor to code. Basically, you can use the normal IDE of your liking or even go wild and code everything in notepad. I'm going to use the Visual Studio Code throughout the whole article.

Folder Structure

An extension, at the bare minimum, only requires two files to work. The first one is index.html or any other HTML file. This file will contain the extension's UI and will be loaded by the Adobe Application at the start-up of our extension. The second one is manifest.xml. This file stores all configuration, such as the plugin's name and the permissions required for it to work. It is required for that file to be in the CSXS directory.

myextension
├── CSXS
│ └── manifest.xml
└── index.html

Extension Configuration

All configuration for an extension is stored in manifest.xml. This is the place where you define the name, the permissions of our extension, and much more. Every valid extension requires this file. A minimal configuration can be found below.

<?xml version="1.0" encoding="UTF-8"?>
<!-- 1 -->
<ExtensionManifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ExtensionBundleId="com.myextension" ExtensionBundleVersion="1.0.0" Version="9.0">
   <!-- 2 -->
   <ExtensionList>
      <Extension Id="com.myextension.panel" Version="0.0.1" />
   </ExtensionList>
   <ExecutionEnvironment>
      <!-- 3 -->
      <HostList>
         <Host Name="PHSP" Version="20" />
         <Host Name="PHXS" Version="20" />
      </HostList>
      <LocaleList>
         <Locale Code="All" />
      </LocaleList>
      <RequiredRuntimeList>
         <!-- 4 -->
         <RequiredRuntime Name="CSXS" Version="9.0" />
      </RequiredRuntimeList>
   </ExecutionEnvironment>
   <DispatchInfoList>
      <Extension Id="com.myextension.panel">
         <DispatchInfo>
            <Resources>
               <!-- 5 -->
               <MainPath>./index.html</MainPath>
            </Resources>
            <UI>
               <!-- 6 -->
               <Type>Panel</Type>
               <!-- 7 -->
               <Menu>My Extension</Menu>
               <Geometry>
                  <!-- 8 -->
                  <Size>
                     <Height>500</Height>
                     <Width>350</Width>
                  </Size>
               </Geometry>
            </UI>
         </DispatchInfo>
      </Extension>
   </DispatchInfoList>
</ExtensionManifest>

The manifest defines an extension bundle, which can include multiple extensions.

  1. ExtensionBundleId contains the name of the whole bundle, ExtensionList contains a list of all included extensions. For example, if the bundle id is set to com.myextension, the extension id should be set to com.myextension.<name of the extension>.
  2. HostList defines Adobe tools supported by the extension. Any tool not listed here will not try to load our custom extension. You can specify the application by using its unique id and version. For example, if you use IDSN code with version 19, our extension will work for InDesign in version 19 and up. At the time of writing, you can use the following 14 codes to target 13 Adobe applications. (Note that there are two keys valid for Photoshop and both are required if you want to create a custom extension for this tool):

    • Photoshop - PHSP
    • Photoshop - PHXS
    • InDesign - IDSN
    • InCopy - AICY
    • Illustrator - ILST
    • Premiere Pro - PPRO
    • Prelude - PRLD
    • After Effects - AEFT
    • Animate (Flash Pro) - FLPR
    • Audition - AUDT
    • Dreamweaver - DRWV
    • Muse - MUSE
    • Bridge - KBRG
    • Rush - RUSH
  3. RequiredRuntime defines a version of CEP for our extension to use.
  4. MainPath defines what HTML file should be loaded initially
  5. Type defines what type our new extension should be. Currently, there are 4 types:

    • Panel - behaves like any standard panel
    • ModalDialog - opens a new extension window on top of the host app and forces the user to interact with it. The user is unable to use the hosted app unless it closes the extension window.
    • Modeless - opens a new extension window but doesn't force the user to interact with it
    • Custom - an invisible extension remains hidden and never becomes visible during its whole life cycle
  6. Menu defines the name that will appear in the dropdown menu of the Adobe application
  7. Size defines the default size in pixels of your extension

Enable custom extension in Photoshop

Before you start creating any custom logic for out extension, first you need to set Adobe application into debug mode. This will allow us to add our own, yet unsigned extension to Photoshop and debug it.

Mac

  1. Open your terminal
  2. Run command

    defaults write com.adobe.CSXS.X.plist PlayerDebugMode 1 && killall -u `whoami` cfprefsd

    CSXS.X - X value depends on the version of the Adobe Application version. In this case you need to use 9. Check version on the Documentation.

Windows

  1. Open regedit
  2. Navigate to HKEY_CURRENT_USER/Software/Adobe/CSXS.X

    CSXS.X - X value depends on the version of the Adobe Application version. In this case you need to use 9. Check version on the Documentation.

  3. Add a new entry PlayerDebugMode of type string with the value of 1.

Having done that, you need to install our extension in proper directory on your machine:

  • Mac - ~/Library/Application Support/Adobe/CEP/extensions/
  • Win 32bit - C:\Program Files\Common Files\Adobe\CEP\extensions\
  • Win 64bit - C:\Program Files (x86)\Common Files\Adobe\CEP\extensions\

When that step is done, you can open the desired Adobe tool and check the extension. Each Adobe tool will load any extension found in the above folders on startup. After you run the desired tool e.g Photoshop, in the menu navigate to Window > Extensions > <Your panel name>. After you open our custom extension, you should see a blank panel.

Blank Photoshop Panel

If nothing happens or your panel's name is not listed under Extensions, double-check if DebugMode is set and if the extension is in the proper directory.

HTML

Creating extension UI in HTML is no different from creating a normal webpage in the browser. You can use any HTML you like and even complicated JavaScript frameworks such as React or Vue.

One important thing to be aware of is persistence. Every time user closes and opens the extension, the panel is fully reloaded. Upon closing the panel, the extension is killed, and upon opening, an extension is initialized from scratch. Even though the extension is killed, cookies and localstorage work normally and are not being cleared. They can be leveraged for storing user data such as authentication tokens and so on.

For this article, you are going to use simple HTML, because you want to display static text.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>My Extension Title</title>
  </head>
  <body style="background: white;">
    Hello World
  </body>
</html>

After running our extension you should see happy Hello World in the Photoshop panel.

Hello World in Photoshop Panel

And just like this you've just created your first custom Photoshop extension. Congratulations! This extension is very simple and basic. In the next part, you will learn how to leverage the Photoshop API.

Part 2

CSInterface

Communication between extension and Adobe Tool is done by a special JavaScript library created by Adobe which is called CSInterface. Developers can use it in order to access the powerful API of the Host Tool such as Photoshop. This library is not imbedded into host tools so if you need to use it you need to download it from Github and import it to your extension. Because it is a simple JavaScript file it can be imported using a standard script tag in HTML of our panel.

<head>
  ...
</head>
<body>
  <script src="CSInterface.js"></script>
  <script>
    var cs = new CSInterface();
    alert('App name: ' + cs.getHostEnvironment().appName);
  </script>
</body>

Once that's done, you can trigger our custom scripts, listen to events and much more. Sky is the limit.

The most important function available in CSInterface is evalScript. This function allows us to trigger a function that has direct access to the Host Tool API. This function accepts two arguments. First is the script which needs to be triggered, and the second is the callback function which will be triggered when our script has finished. Script is a function that is available in the ExtendScript. This argument needs to be passed as a string. It is important to convert all necessary arguments of our custom function to string as well before evaluating it.

var csInterface = new CSInterface();
var script = 'customFoo("' + arg + '")';

csInterface.evalScript(script, function() {
  console.log('Done');
});

Debugging

Rarely do things just work the first time around in the programming world. Because of that you need to have a way of checking what is wrong with our code. In order to debug an extension panel in CEP, you need two things - Enabling the custom extension in Photoshop and a .debug file. The first element was described in the previous part of this article.

.debug file

The .debug file is a special file that tells Adobe tools that you want to attach a debugger to test our extension. It is necessary for this file to be located at the root of our extension folder. An example of a simple extension structure can be seen bellow.

myextension
├── CSXS
│ └── manifest.xml
├── index.html
└── .debug

The .debug file is written in XML and contains only two important elements.

<ExtensionList>
  <Extension Id="com.example.helloworld">
    <HostList>
      <Host Name="PHXS" Port="8088"/>
      <Host Name="PHSP" Port="8088"/>
    </HostList>
  </Extension>
</ExtensionList>
  1. ExtensionID - must contain the id of the extension you want to debug. This id can be found in the manifest.xml file. For more information please read Extension Configuration.
  2. HostList - contains a list of all host tools to which you want to attach the debugger. The Host element has two attributes - Name and Port. The name contains the host id, for more information please read Extension Configuration. Port contains the port on which you will be attaching our debugger.

cefclient

Once the .debug file is created with proper configuration, you have a way to attach s debugger to the proper port. This is the place where you need a special app created by Adobe, called cefclient. This app is basically a specialised version of the Chromium. It can be download from Adobe's Github page.

Home screen of cefclient

Once you've created the .debug file and installed cefclient, you can start debugging. For that, you are going to use ports defined in the .debug file. In the address bar, you need to type localhost:<port>. <port> should correspond with the value inside .debug.

If everything is fine with our extension, you should be presented with the view in which you can select which extension you want to debug. If you see an error, please make sure you have the proper host name and port typed in your debug file. Also, make sure your extension is running.

Selection of Extension in cefclient

And after selecting the desired extension you have access to a full set of tools provided by Chromium. These dev tools are basically standard dev tools available in the Chrome browser or any other browser. In this place, you can validate HTML, CSS, and JavaScript code of your extension.

Active Debbuger in cefclient

Node.js

Working with Node.js in CEF client is really simple and straightforward and only requires 2 lines of configuration. In your manifest.xml under <Resources> just paste this lines:

<CEFCommandLine>
  <Parameter>--enable-nodejs</Parameter>
  <Parameter>--mixed-context</Parameter>
</CEFCommandLine>
  • --enable-nodejs - it enables Node.js in the Chromium. Thanks to that you can use all of Node.js API without any custom configuration. This configuration can be summarized with a famous cite by Todd Howard from Bethesda Games Studio - It just works!
  • --mixed-context - it enables working with mixed content in the Chromium so you will not get any unexpected errors

It should look like this:

...
<Resources>
  <MainPath>./index.html</MainPath>
  <CEFCommandLine>
    <Parameter>--enable-nodejs</Parameter>
    <Parameter>--mixed-context</Parameter>
  </CEFCommandLine>
</Resources>
...

Once that's done, you can use all native modules just by requiring them:

<button onclick="getfileslist()">List files</button>

<script>
  function getfileslist() {
    // Get Path to Extension directory
    var cs = new CSInterface();
    var path = cs.getSystemPath(SystemPath.EXTENSION);

    var fs = require('fs');
    var list = fs.readdirSync(path);
    alert(list.join('\n'));
  }
</script>

But you are not limited to using only native modules. You can install all modules compatible with the given Node.js version in CEP. To use them, just install them in your standard npm way. That's all. And here is an example:

First, you install our desired package using a command in the terminal:

npm install systeminformation --save

That will create a standard node_modules directory which will contain all necessary files. Once this is done you can use it in our extension:

<button onclick="getsysteminfo()">List files</button>

<script>
  function getsysteminfo() {
    var si = require('systeminformation');

    si.cpu(function(data) {
      console.log('CPU-Information:');
      alert(data);
    });
  }
</script>

As you see, this is not very complicated. For a real-life example I recommend you to look at RSSReader. This repository contains more advanced examples of using Node.js inside the host tool.

Events

There is a moment when creating an application that you want our code to run by itself after some specific action and for that you use events. There is no difference here. While creating an extension, you can listen to built-in events and even our custom ones. For listening, creating, and sending events, you're going to use CSInterface.

Event passing within extension Panel

Listening to events using CSInterface is very similar to listening to normal events in the browser.

var cs = new CSInterface();

cs.addEventListener('foo', function(e) {
  alert('event: ' + e);
});

And just like that our code will be run every time event foo is triggered. In order to trigger a custom event you need to create a special event object using CSEvent class. Class CSEvent is declared in the CSInterface file so for further details it’s easiest to check the source.

var cs = new CSInterface();
var event = new CSEvent('foo');
event.data = 'Hello world!';

cs.dispatchEvent(event);

Event passing from Panel to Host Tool

To communicate between JavaScript and ExtendScript, you once again use CSInterface. This time, however, you need to use the PlugPlugExternalObject library as well. PlugPlugExternalObject allows your ExtendScript code to create and dispatch CEP events. This library should be integrated into each of the modern Adobe tools but just in case you are going to use condition here.

Listening for the event sent from ExtendScript does not differ from listening to normal events in the Panel. Once again you are going to use addEventListener. This time you are going to listen for a custom event - cep.extendscript.event.message.

var csInterface = new CSInterface();

csInterface.addEventListener('cep.extendscript.event.message', function() {
  console.log(event.data);
});

Once that's done, you can navigate to ExtendScript to create a trigger for our custom event. Before you dispatch any event you need to make sure PlugPlugExternalObject is available and it is loaded. This is done once, preferably at the start of your script.

try {
  xlib = new ExternalObject('lib:PlugPlugExternalObject');
} catch (e) {
  alert(e);
}

Once PlugPlugExternalObject you can dispatch events. You can use a simple function to simplify this process and make sure to check if needed library is loaded.

function dispatchCepEvent(in_eventType, in_message) {
  if (xlib) {
    var eventObj = new CSXSEvent();
    eventObj.type = in_eventType;
    eventObj.data = in_message;
    eventObj.dispatch();
  }
}

dispatchCepEvent('cep.extendscript.event.message', 'Update the UI');

Listening to Host Tools events

In order to listen to Host Tool events, you need two things - an event id, and an ExtendScript file which needs to be run when the event is triggered.

The Event Id is a 4 letter string, which can be found in the documentation. In this case, you are going to use Adobe Photoshop Documentation because our extension needs to work in Photoshop. So, for example, if you want to run our code every time the user saves the file in the Photoshop, you need to use the save event. BUT some event codes consist of letters and space. For example, the open event's event id is Opn. The space after n is very important so make sure you use the proper event id.

Once you have event id, you need to create a new ExtendScript file. This file will be run every time the event is triggered. Just like a callback. To get a file you need to use the File class. This class accepts the path to a file and returns a reference to it. The path can be hardcoded to, for example, C:\\SaveCallback.jsx but then the user would need to download your script to the proper directory on their drive. A much better solution is to use app.path. This will return the path to an extension on the user drive.

var eventFile = new File(app.path + '/jsx/save.jsx');

Host Tool events need to be attached in ExtendScript to notifiers. This class has a simple add function that accepts an event id and a callback file.

var eventFile = new File(app.path + '/jsx/save.jsx');
app.notifiers.add('save', eventFile);

Now, let's take a look at save.jsx. All you need to do to run your custom script and make sure it will not break is use try and catch. Just like this:

try {
  alert('file saves');
} catch (e) {
  alert(e);
}

And that's all. If you want you can trigger custom events in this try in the way it was presented in the previous section.

Packaging and Distribution

In the previous section, you learned how to create your desired extension, but now, after all of the development processes is done you need to share your creation with the world.

Packaging

Adobe tool extensions are packaged as .zxp files. They are basically a .zip file with signing details. Before you distribute our extension, you need to sign it. This means that you need to create a special certificate and stamp it on our extension in order for Adobe tool to see our package as safe to run.

Currently, there are 3 ways of signing an extension:

In this article, I'm going to use CC Extensions Signing Toolkit because it works and does not require any special account at Adobe site. It is command-line tool and will allow us to create a certificate and sign our extension.

First, you need to create a self-signed certificate. For that you are going to run CC Extensions Signing Toolkit in our terminal with property -selfSignedCert. A certificate requires some data such as - country, region oranization username, userpassword, outputFile. Output file will determine the name of our newly created certificate and it needs to have a .p12 extension. For all available options, you can refer to official Adobe Extension SDK documentation PDF.

# ZXPSignCmd -selfSignedCert country region organization username userpassword outputFile
./ZXPSignCmd -selfSignedCert PL Poland Cognifide KacperRogowski password123 myCert.p12

After that's done, the only thing remaining is to sign our extension. For that, once again you're using the CC Extensions Signing Toolkit. This time you need to use the -sign property. This requires options such as - sourceFolder, outputFile, certificate, password. After running this command, myExtension.zxp should be created.

# ZXPSignCmd -sign sourceFolder outputFile certificate password
./ZXPSignCmd -sign "../extensionSourceFolder" myExtension.zxp myCert.p12 password123

Distribution

After creating the .zxp archive, the last remaining step is to share it with the users. If your extension is intended just for your team or company, you can just share this file with them and it is done. Users can install your new extension with a command-line tool which can be found in Adobe Documentation or with the small app ZXPInstaller.

For widespread distribution, it is recommended to share your creation using Adobe’s Add-ons site. Creative Cloud service operates an extension repository called Adobe Add-ons. Extensions can be browsed, installed, and managed from the browser without any extra steps or knowledge. The installation process is done behind the scenes so the user only needs to click Install and wait for the installation to finish. Free extensions can be self-signed so there is no extra cost involved. The site supports also paid and subscription models, but you’ll need a purchased proper certificate for those.

Each extension is reviewed and must meet approval guidelines to become available for users to use. This process may take around 10 business days so you need to be patient.

To get started with distributing extension using Adobe Add-ons site, go to Adobe Exchange Program and press Join the Exchange Program. After that, press Create Listing. You will be navigated to the wizard so you just need to follow the steps and input all necessary data.

Summary

I hope you find this article helpful on your path to becoming a master in using Adobe tools. Of course, the content of this article covers only a fraction of the things made possible by ExtendScript and CEP and I highly recommend you read the official documentation. Below you can find some useful links in which you can find any help or information when you're stuck during your development journey. Good Luck!