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

  • Create a new Xcode (macOS) or Visual Studio (Windows) project targeting a dynamic library.
  • Add the UXP Hybrid Plugin SDK src/api path to your project's include paths.

2. Write Your C++ Logic

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

Copied to your clipboard
#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:

Copied to your clipboard
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:

  • Manifest version 6 or above.
  • The addon field with the name of your uxpaddon.
  • The enableAddon permission.
Copied to your clipboard
{
"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:

Copied to your clipboard
📁 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.

Asynchronous Operations

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

  • Initialization and termination routines run on the main thread of the host application.
  • JavaScript calls to addon functions run on a dedicated scripting thread (never the main thread).

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.
Copied to your clipboard
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:

Copied to your clipboard
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:

  • JavaScript: use the UDT Debug tool to set breakpoints and inspect variables.
  • C++: attach your IDE's debugger to the running Premiere process. In most IDEs this is found under Debug > Attach to Process.

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:

  • macOS arm64 (Apple Silicon)
  • macOS x64 (Intel)
  • Windows x64

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.

3. Code sign and notarize (macOS)

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

  • Self-signed or test certificates are not accepted.
  • The certificate must be valid for at least one year.
  • Only the .uxpaddon binaries need signing—the rest of the plugin bundle (JavaScript, HTML, CSS, manifest) does not.

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:

  • The plugin installs without errors.
  • The uxpaddon loads correctly on each supported platform.
  • No security warnings appear (if binaries are properly signed).

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

  • Privacy
  • Terms of Use
  • Do not sell or share my personal information
  • AdChoices
Copyright © 2026 Adobe. All rights reserved.