Building Hybrid Plugins

A step-by-step guide to building, configuring, and shipping a UXP Hybrid plugin.

Building a uxpaddon

1. Create a Native Library Project

2. Write Your C++ Logic

Include the main header and register your initialization and termination routines:

#include "UxpAddon.h"

addon_value init(addon_env env, addon_value exports) {
    // Register functions and properties on 'exports'
    return exports;
}

void terminate() {
    // Cleanup logic
}

UXP_ADDON_INIT(init);
UXP_ADDON_TERMINATE(terminate);

Within your init function, use the addon APIs (defined in UxpAddonShared.h via addon_apis) to register native functions that will be callable from JavaScript. These APIs are invoked as UxpAddonApis.uxp_addon_***().

3. Compile and Rename

Build the dynamic library (.dylib on macOS, .dll on Windows) and rename the file extension to .uxpaddon.

Using a uxpaddon in JavaScript

Once compiled, the addon is loaded with require() and its exported functions are available immediately:

const addon = require("sample-uxp-addon.uxpaddon");
let result = addon.first_function();

The addon behaves like any other JavaScript moduleβ€”you can call functions, read properties, and pass data back and forth between JavaScript and C++.

Manifest Configuration

Hybrid plugins require a few specific settings in the manifest.json:

{
  "manifestVersion": 6,
  "host": {
    "app": "premierepro",
    "minVersion": "25.6.0",
  },
  "addon": {
    "name": "sample-uxp-addon.uxpaddon"
  },
  "requiredPermissions": {
    "enableAddon": true
  }
}

Plugin Structure

The uxpaddon binaries must be placed in a specific directory layout within your plugin bundle, organized by platform and architecture:

πŸ“ root directory
β”œβ”€β”€ πŸ“„ manifest.json
β”œβ”€β”€ πŸ“„ main.js
β”œβ”€β”€ πŸ“ mac
β”‚   β”œβ”€β”€ πŸ“ x64
β”‚   β”‚   └── βš™οΈ sample-uxp-addon.uxpaddon (dylib)
β”‚   └── πŸ“ arm64
β”‚       └── βš™οΈ sample-uxp-addon.uxpaddon (dylib)
└── πŸ“ win
    └── πŸ“ x64
        └── βš™οΈ sample-uxp-addon.uxpaddon (dll)

If the directory structure is incorrect, the plugin will fail to load with a "Plugin Manifest Validation Failed" error in the UDT logs.

data-variant=info
data-slots=text
You can try the pre-compiled template-plugin included in the SDK to quickly verify your setup. Load it via UDT and find it under the Window > UXP Plugins menu.

Asynchronous Operations

It is important to understand the threading model when working with uxpaddons:

When your addon needs to perform work on the main thread (e.g., interacting with host-specific APIs) and return results to JavaScript, you must use the asynchronous scheduling APIs. These operations are exposed to JavaScript as Promises.

The pattern involves three steps:

  1. From the scripting thread: create a deferred promise and schedule work on the main thread.
  2. On the main thread: perform the task, then schedule the result back to the scripting thread.
  3. Back on the scripting thread: resolve or reject the promise.
addon_value MyAsyncOperation(addon_env env, addon_callback_info info) {
    // Extract and convert arguments to standard C++ types
    // (addon_value is transient and cannot be used after this function returns)
    std::string myArg = ExtractString(env, argValue);

    // Create a promise for the JavaScript caller
    addon_deferred promiseValue = nullptr;
    addon_value deferred = nullptr;
    apis.uxp_addon_create_promise(env, &deferred, &promiseValue);

    // Schedule work on the main thread
    apis.uxp_addon_schedule_on_main_queue(env, MainThreadTask, ...);
    return deferred;
}

void MainThreadTask(...) {
    // Perform the task on the main thread...

    // When done, schedule the result back to the scripting thread
    apis.uxp_addon_schedule_on_javascript_queue(env, ScriptThreadTask, ...);
}

void ScriptThreadTask(...) {
    apis.uxp_addon_open_handle_scope(env, &scope);

    // Resolve (or reject) the JavaScript promise
    addon_value resultValue = nullptr;
    apis.uxp_addon_resolve_deferred(env, deferred, resultValue);
    apis.uxp_addon_close_handle_scope(env, scope);
}

The template-dev project in the SDK includes a working implementation of this pattern (see MyAsyncEcho in module.cpp).

File System Access

Hybrid plugins benefit from relaxed UXP sandbox restrictions. You can access the file system using direct paths:

let entry = '/path/to/file.txt';

This is unlike standard UXP plugins, which must use require('uxp').storage.localFileSystem to access files outside the sandbox. See the Filesystem Operations recipe for more details on standard file system access.

Debugging

Hybrid plugins have both a JavaScript and a C++ layer, each requiring its own debugging approach:

Packaging and Distribution

Hybrid plugins follow the same packaging workflow as standard UXP plugins, but with additional steps for native binaries. Work through the checklist below before distributing your plugin.

1. Verify the plugin structure

Make sure your .uxpaddon files are placed in the correct directory layout (see Plugin Structure above). The folder hierarchy is strictβ€”if it doesn't match, the plugin will fail to load with a "Plugin Manifest Validation Failed" error in UDT.

2. Build for all required architectures

Compile your uxpaddon for each supported platform:

If you don't have access to all platforms natively, consider using a virtual machine (e.g., VMware Fusion or Parallels). Keep in mind that Apple Silicon Macs cannot virtualize Windows x64β€”only Intel Macs can, so dedicated hardware may be required. For creating macOS universal binaries, see Apple's guide.

data-variant=warning
data-slots=text
You can package and install a Hybrid plugin with only a subset of architecturesβ€”the plugin will simply fail to load on unsupported platforms. However, the Creative Cloud Marketplace requires all three architectures; the Developer Distribution portal will reject your .ccx package if any is missing. Partial architecture support is only viable for independent or enterprise distribution.

3. Code sign and notarize (macOS)

The macOS .uxpaddon executables must be signed and notarized with a valid Apple Developer ID certificate. Requirements:

See the FAQ for more details on Apple Developer ID requirements.

4. Set your plugin ID

Before packaging, ensure the id in your manifest.json is correct. If you plan to publish to the Creative Cloud Marketplace, obtain the ID from the Developer Distribution portal and make sure it matches. See Mind your Plugin's ID for details.

5. Package with UDT

Open the UXP Developer Tool, locate your plugin in the workspace, click the β€’β€’β€’ menu and select Package. This creates a .ccx installer file. See Package with the UXP Developer Tool for a walkthrough.

6. Test the installation

Install the .ccx file on a clean system (or at least one without your development environment) to verify:

data-variant=warning
data-slots=text
Since Hybrid plugins include native code, users will be prompted for OS administrator credentials during installation and updates. This is expected behavior.

For the full distribution workflow (Marketplace submission, independent distribution, enterprise deployment), see the Share & Distribute section.