Migrating a local Node.js + Puppeteer workflow from CEP to UXP (Premiere Pro): Need architectural guidance

Hello everyone,

I am a sports video editor and a self-taught coder. For the past few month, I’ve been running a custom local broadcast console inside Premiere Pro to generate dynamic overlays (scoreboards, team rosters, timers) for rugby match highlights.

Because traditional MOGRTs were too heavy for rapid iteration and batch exporting, I built a hybrid architecture using CEP, React, and a local Node.js server. I am now looking to migrate this entire project from CEP to UXP to future-proof the tool, but I need some guidance to ensure I can port it without breaking my core rendering engine.

My Current CEP Architecture (How it works today)

Since there is no native way to render HTML/CSS animations into a transparent video or image sequence directly within the Premiere Pro panel, I had to build an external renderer:

  1. Frontend: The CEP panel runs a React 18 / TailwindCSS UI where I configure the match data.

  2. Local Server: The panel communicates with a local Node.js server running in the background.

  3. Headless Rendering (The core hack): The Node server triggers a headless Chromium instance (Puppeteer). Puppeteer loads the specific React component, plays the CSS animation, and extracts a 4K PNG frame with an Alpha channel (omitBackground: true) at the exact peak of the animation.

  4. Timeline Injection: The CEP panel uses CSInterface.evalScript to call an ExtendScript function that imports the resulting .png file and places it directly onto the V2 track of the active sequence.

Note: This entire workflow runs 100% offline via the local FileSystem, and a single render/injection loop takes about 1.5 seconds.

The Goal: Migrating to UXP

I want to transition the panel UI and the scripting logic to UXP to benefit from the modern V8 engine and Spectrum UI, but I must keep my external Node.js + Puppeteer pipeline intact (as it is the only way to get my HTML-to-PNG-Alpha renders).

My Questions for the UXP Experts

To ensure a 1:1 migration without regressions, I would appreciate your insights on the following points:

  1. Localhost Communication: In CEP, I used standard XHR/fetch to talk to my local Node server. In UXP, are there any specific manifest permissions or limitations I need to be aware of to make standard fetch() calls to a localhost port?

  2. Replacing ExtendScript for Timeline Injection: I need to replace my .jsx script. Does the current Premiere Pro UXP DOM fully support importing a media file from a local absolute path and inserting it at the playhead on a specific video track (e.g., Video Track 2)? If so, could you point me to the relevant UXP classes/methods for track targeting?

  3. File System Access: My Node server writes the PNG to a local folder, and Premiere needs to read it. Using the UXP fs module, what is the cleanest way to pass an absolute file path to the Premiere UXP DOM for import without triggering user-facing file picker dialogs?

Any advice, code snippets, or pointers to the specific Premiere Pro UXP documentation for sequence/track manipulation would be incredibly helpful.

Thank you in advance for your time and expertise!

Best regards,

Jérôme

1 Like

First off, congrats on building such a robust unique system. I don’t think I’ve heard of anyone rendering graphics offline in Puppeteer and bringing those into Premiere before.

You should be able to migrate most of your project to UXP today with some caveats:

1 - Frontend

As the UXP UI is quite limited, unless you only need simple buttons / dropdowns without adjusting styling, I’d suggest looking into a WebView UI. This will give you the full browser capabilities you’re used to in CEP.

Additionally, our open source framework Bolt UXP comes with a WebView UI options setup out-of-the box so you get type-safe two-way messaging ready to use: GitHub - hyperbrew/bolt-uxp: A lightning-fast boilerplate for building Adobe UXP Plugins in Svelte, React, or Vue built on Vite + TypeScript + Sass · GitHub

2 - Local Node.js Server

Setting up a Node.js server will be more tricky in UXP since UXP does not ship with Node like CEP did. So instead, if you must have a Node.js instance and can’t rely on browser packages to do what you need, you’ll need to add node to a UXP plugin in 1 of 2 ways:

2.1 - Ship Mac/Win Binaries & call via execSync()

You would need to ship the signed binaries for Node.js for all platforms, and call the one you want with your Node.js script per OS via execSync() with UXP C++ Hybrid Plugins. Bolt UXP comes with a pre-signed hybrid plugin with execSync() out of the box so you can use without building/signing/notarizing.

2.2 - Embed Node.js in the C++ Hybrid Plugin

Alternatively, you can either embed the binaries or build the source directly into the C++ Hybrid portion of your UXP plugin. This will require more work but would be fewer files to transport and manage in the long run.

3 - Localhost Communication

You will need to add whatever localhost port full URL(s) you need to communicate with your Node.js Server to your UXP manifest NetworkPremissions, then you’ll be able to communicate via fetch() from either the UXP layer or WebView layer depending on your setup.

4 - Replacing ExtendScript for Timeline Injection

Premiere UXP can add clips to the timeline with createInsertProjectItemAction() and createOverwriteItemAction() Check out examples in the example UXP Plugin.

5 - File System Access

You can access file paths directly in UXP by using lfs.getEntryWithUrl() (docs here), however a lot of Premiere UXP methods such as importFiles() only need the path string itself not a UXP file entry, just make sure the path you pass is OS-native. A useful way to do this is by generating a URL Entry and getting the native path from it:


(await lfs.getEntryWithUrl("path/to/file")).nativePath

Hope that helps you get started, feel free to reach out if you have questions.

2 Likes

Hi Justin,

Thank you so much for taking the time to read through my architecture and for providing such an incredible and detailed answer! This is a massive breakthrough for my project. The fact that Bolt UXP provides a pre-signed C++ Hybrid Plugin with execSync() out-of-the-box and native WebView support is an absolute game-changer. I am definitely going to rebuild my UXP migration from scratch using Bolt UXP as the foundation.

Before I dive fully into the codebase, I want to make sure I don’t hit any architectural dead-ends. Could you clarify a few quick technical details regarding your suggestions?

1. Asynchronous Execution vs. execSync() blocking: Since generating the graphic via Puppeteer takes about 1.5 seconds, my biggest fear is freezing the Premiere Pro UI. I noticed you specifically mentioned execSync(). Does the Bolt UXP hybrid plugin also support an asynchronous exec() or spawn() method? I need to ensure that the panel and the host app remain fully responsive while the Node.js sidecar is doing its heavy lifting in the background.

2. Packaging the heavy Chromium binary: Puppeteer requires a local Chromium binary (around 150MB) to run headless. Does the Bolt UXP Vite bundler (or the .ccx packaging process) handle copying such large external sidecar directories smoothly? If so, is there a specific folder in the Bolt UXP boilerplate (like public/ or assets/) where I should place the compiled Node executable and the Chromium folder so they get packaged correctly?

3. Targeting a specific Video Track (V2): Regarding createOverwriteItemAction(), I need to ensure the PNG is injected exactly on Video Track 2 (to act as an overlay without overwriting the main footage on V1). Do these new DOM actions accept a specific track index/target as an argument, or do they rely on the user’s current track targeting/patching state in the Premiere timeline UI?

4. Teardown / Zombie Processes: If I spawn my Node.js server via the hybrid plugin, is there a recommended lifecycle hook in UXP (like an onBeforeUnload equivalent) where I can safely send a kill command to the Node server when the user closes the panel or quits Premiere Pro? I want to avoid leaving phantom Chromium instances running in the OS background.

Thank you again for creating such an amazing open-source framework and for your invaluable guidance!

Best, Jérôme

Sure thing, glad to hear you’re going to try out Bolt for your project!

1 - Async Execution

We are actually working on an asynchronous exec() method for Bolt currently, hoping to have that out in the coming weeks…

2 - Packaging Large Files

Yes you can bundle large files as a .ccx is essentially just a zip archive. The readme will give you details on where to place files but yes the public dir is one of them. We’re using that on a current project to ship 25MB binaries, but there’s no limit on larger files, will just take longer to move around of course. You can always make separate Mac/Win builds to lighten the load if you want.

3 - Targeting a specific Video Track

Yes, you can set the audio and video track with createOverwriteItemAction() as the 3rd and 4th arguments

4. Teardown / Zombie Processes

You can use the UXP lifecycle hooks for this to capture when a plugin is closed, but note the limitations in Premiere at the moment : https://developer.adobe.com/premiere-pro/uxp/plugins/tutorials/add-lifecycle-hooks/


As you’re diving in, feel free to join our Discord as well, we’ve got a bunch of Bolt devs in there sharing tips and tricks: Discord — Resources | Hyper Brew

2 Likes

Hi Justin,

Thank you for the quick and precise answers. This clears up the very last technical roadblocks for my migration!

It’s great news that an async exec() is on the roadmap. In the meantime, I will structure my sidecar binaries in the public directory and utilize the 3rd and 4th arguments of createOverwriteItemAction() for the track targeting as you suggested. I’ll also look into the UXP lifecycle hooks for the teardown process.

I will definitely join the Hyper Brew Discord server to follow the community’s progress.

Thanks again for your time and for maintaining such a valuable tool for the developer community!

1 Like

The UXP wall has fallen! :tada:

Following our discussions regarding the limitations of UXP with Puppeteer and Node.js, I am pleased to announce that the migration of my project’s “Zero-Mogrt” architecture (R-CAST / RESUM’MATCH) from CEP to UXP is now fully functional.

Here is a detailed feedback on how this migration was executed, the hurdles encountered with the new API, as well as an objective analysis of the strengths and weaknesses of UXP for this kind of offloaded workflow.

1. What was achieved and how (The Daemon Architecture)

In accordance with the architectural recommendations discussed, since direct integration of Puppeteer and child_process within the panel is impossible under UXP, the system was split into a client/server (Daemon) architecture:

  • The Client (UXP Plugin): A statically compiled React 18 interface that interacts exclusively with the new Premiere Pro UXP DOM.

  • The Server (Local Node.js Backend): A background process handling the Puppeteer rendering and the “RAM Pipe” to FFmpeg to generate .mov files (qtrle codec with Alpha channel).

  • Communication: The UXP interface communicates with the local server via standard network calls (REST API / WebSockets), while communication between the view (WebView) and Premiere goes through the native UXP bridge (enableMessageBridge).

2. Technical hurdles encountered and resolved

The transition from ExtendScript to the new Premiere Pro UXP API required working around several paradigms:

  • The new DOM and timeline injection: Since the old synchronous track.overwriteClip() method no longer exists, I had to adopt a transactional approach using premiere.SequenceEditor.getEditor() and the creation of specific actions (createOverwriteItemAction).

  • Thread safety (Locked Access): Any attempt to modify the sequence returned an error demanding locked access. The solution was to encapsulate the timeline read/write operations within the asynchronous project.lockedAccess() method.

  • Casting project objects: Under UXP, the generic ProjectItem object doesn’t natively have the .getItems() method to iterate through folders (Bins). I had to implement native casting via premiere.FolderItem.cast(projectItem).

3. Strengths of UXP compared to CEP for this project

  • Unified transactions (Undo/Redo): This is the major advantage. Under ExtendScript, injecting multiple media files meant each action had to be undone separately. UXP, via project.executeTransaction(), allows wrapping the entire overlay insertion into a single atomic action that can be undone with a single Ctrl+Z.

  • The death of ExtendScript: Moving to a native modern JavaScript (ES6+) environment secures the code and removes the need to juggle serialized strings via the old CSInterface.js.

  • Message Bridge stability: The enableMessageBridge: "localAndRemote" bridge proves to be much more robust and faster for communication between the WebView and the Premiere Pro host.

4. Weaknesses of UXP compared to CEP for this project

  • Loss of native system access: UXP’s strict sandboxing prevents the use of vital modules for hybrid broadcast workflows (child_process, low-level access). This forces the maintenance of a complex architecture (creating and managing an external Daemon).

  • Deployment complexity: Where a CEP panel could (under certain conditions) bundle Node executables, the UXP version requires distributing and installing two distinct software entities on the editing workstation (the UXP plugin on one side, and the local server executable on the other).

In conclusion, although the learning curve for the new asynchronous DOM is steep, the UXP model coupled with a “Localhost Daemon” architecture offers superior stability for intensive production workflows.

Thanks again to @justin2taylor for the architectural guidance during the design phase.