CEP for After Effects 2026:
The Underground Plugin Guide

BeatMarker AE screenshot

Why CEP in 2026? Isn't It Dead?

Every blog post about After Effects plugin development in the last three years starts the same way: "CEP is deprecated. Use UXP." And they're technically right — Adobe is moving everything to UXP, their modern plugin runtime. Except for one small problem: After Effects 2026 still doesn't support UXP panels for audio processing.

UXP in AE runs in a restricted environment that makes it impractical for plugins that need to decode audio files in real time. CEP, on the other hand, runs inside a full Chromium browser context with Node.js access — which means the Web Audio API, music analysis libraries, file system access, the whole stack. For BeatMarker AE (a plugin that detects BPM from audio layers), CEP was the only viable path.

So Claude Code dug into CEP in 2026 — and what it found was a partially undocumented, slightly broken, but absolutely functional platform. This is the guide that didn't exist.

"The documentation said CEP was dead. The plugin works great."

The Install Path Adobe Forgot to Tell You

Every tutorial on CEP extensions shows you the standard install paths. You drop your extension folder there, restart the app, done. Except in After Effects 2026, those paths simply don't work. The app loads nothing. No error. No log. Just silence.

The paths that don't work in AE 2026:

%APPDATA%\Adobe\CEP\extensions\         ← nothing
C:\Program Files (x86)\Common Files\Adobe\CEP\extensions\  ← nothing

The path that does work:

C:\Program Files\Adobe\Adobe After Effects 2026\Support Files\<your-folder>\

Claude Code found this by inspecting a working extension that ships with AE 2026 — Adobe's own learning panel, com.adobe.LABOR.LearningPanel. By locating where that panel was installed and comparing its manifest format, Claude Code reverse-engineered what AE 2026 actually expects. It was the only reliable source of truth.

⚠️ Heads Up Installer scripts that read the AE path from the Windows registry may fail if AE is installed on a non-default drive. Always add a fallback to the hardcoded path above.

The Manifest: Copy From the Wrong Version and Nothing Works

CEP manifest files have versioning, and the version matters enormously. Most Stack Overflow answers and tutorials reference CEP manifests for older versions of Creative Cloud apps — version 6.0, version 12.0. In AE 2026, those manifests load silently and do nothing.

The exact manifest format required by AE 2026:

<?xml version="1.0" encoding="UTF-8"?>
<ExtensionManifest Version="7.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <ExtensionList>
    <Extension Id="com.yourdomain.yourplugin" Version="1.0.0">
      <HostList>
        <!-- No decimal on minimum version -->
        <Host Name="AEFT" Version="[22,99.9]"/>
      </HostList>
      <DispatchInfoList>
        <Extension Id="com.yourdomain.yourplugin">
          <DispatchInfo>
            <Resources>
              <MainPath>./index.html</MainPath>
              <CEFCommandLine>
                <Parameter>--allow-file-access</Parameter>
                <Parameter>--allow-file-access-from-files</Parameter>
                <Parameter>--enable-nodejs</Parameter>
                <Parameter>--mixed-context</Parameter>
              </CEFCommandLine>
            </Resources>
            <Lifecycle>
              <AutoVisible>true</AutoVisible>
            </Lifecycle>
            <UI>
              <Type>Panel</Type>
              <Geometry>
                <Size><Height>580</Height><Width>260</Width></Size>
              </Geometry>
            </UI>
          </DispatchInfo>
        </Extension>
      </DispatchInfoList>
    </Extension>
  </ExtensionList>

  <ExecutionEnvironment>
    <HostList>
      <Host Name="AEFT" Version="[22,99.9]"/>
    </HostList>
    <LocaleList>
      <Locale Code="All"/>
    </LocaleList>
    <!-- Not 12.0 — use 11.0 -->
    <RequiredRuntimeList>
      <RequiredRuntime Name="CSXS" Version="11.0"/>
    </RequiredRuntimeList>
  </ExecutionEnvironment>

</ExtensionManifest>
❌ Wrong✅ Required for AE 2026
ExtensionManifest Version="6.0"Version="7.0"
RequiredRuntime Version="12.0"Version="11.0"
Host Version="[22.0,99.9]"Version="[22,99.9]" (no decimal on minimum)
Missing CEFCommandLine flagsAll 4 flags required for Node.js + file access

PlayerDebugMode: The Hidden Unlock

Adobe requires extensions to be code-signed for production use. During development, unsigned extensions are silently ignored — unless you set a registry key that puts CEP in debug mode.

You need to set PlayerDebugMode = 1 (as a String value) in both of these registry paths:

HKCU\SOFTWARE\Adobe\CSXS.12\PlayerDebugMode = "1"
HKCU\SOFTWARE\Adobe\CSXS.13\PlayerDebugMode = "1"

The installer for BeatMarker AE handles this automatically via a .bat file using reg add. Without this step, the extension doesn't appear in the Window menu — and you get no error, no log, nothing to debug against.

"Hours of debugging. The extension simply did not appear. One registry key fixed everything."

The Two Worlds: Browser Context and ExtendScript

CEP plugins live in two separate environments that cannot directly call each other. Your UI (HTML/JS) runs in a Chromium browser. After Effects APIs run in ExtendScript — Adobe's ES3-based scripting language that hasn't meaningfully changed since 2005.

The only bridge between them is CSInterface.evalScript():

// Browser context (your UI)
const cs = new CSInterface();

// Call an ExtendScript function with a JSON argument
function callExtendScript(fnName, arg) {
  const payload = JSON.stringify(JSON.stringify(arg)); // double-stringify!
  cs.evalScript(`${fnName}(${payload})`, (result) => {
    const data = JSON.parse(result);
    // handle data
  });
}

The double stringify is not a bug — it's required. evalScript constructs a JavaScript call string. The outer JSON.stringify produces a quoted string literal that ExtendScript receives as its argument. The inner stringify is the actual payload that JSON.parse unpacks on the other side.

// ExtendScript (hostscript.jsx) — ES3 environment
#include "json2.js"  // JSON polyfill required!

function bmCreateMarkers(jsonArg) {
  var data = JSON.parse(jsonArg);
  var comp = app.project.activeItem;
  // ... do After Effects stuff
  return JSON.stringify({ ok: true });
}
🚨 Critical Gotcha ExtendScript may have a JSON object without JSON.parse. Check for the method specifically — typeof JSON.parse === 'undefined' — before polyfilling. Checking only typeof JSON === 'undefined' fails silently.

Audio Base64: The Invisible Newlines That Break Everything

Reading an audio file from the browser context in CEP uses cep.fs.readFile. It returns Base64. Simple enough — except the Base64 string has newlines embedded every 76 characters, and atob() throws InvalidCharacterError on whitespace.

The fix is one line, but you will waste 45 minutes before you figure this out:

// ❌ This crashes with InvalidCharacterError
const buffer = atob(result.data);

// ✅ Strip whitespace first
const bin = atob(result.data.replace(/\s+/g, ''));
const bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
const audioBuffer = bytes.buffer;

Once you have the ArrayBuffer, it goes straight into the analysis pipeline — WAV decoder, beat detection, all of it. The browser context in CEP has full Web Audio API access, which is exactly why CEP was chosen over UXP for this plugin.

"44 minutes of debugging an audio crash. One regex solved it."

Markers That Show Too Much: The setParameters Secret

After Effects markers have several text fields: comment, chapter, URL, frame target. Every one of them shows up on the timeline marker shield — that little flag icon that appears on your layer. For a beat detection plugin that creates hundreds of markers, having visible text on every single one is a disaster for readability.

Claude Code went through every field in the MarkerValue API to find which ones were invisible:

MarkerValue FieldVisible on Timeline Shield?
commentYes ❌
chapterYes ❌
urlYes ❌
frameTargetYes ❌
setParameters({ b: '1' })No ✅

setParameters() is a key-value store that's part of the marker but invisible on the timeline. BeatMarker uses it to store the beat position (1–4) in each marker. This means the timeline shows clean color-only markers — exactly what a video editor needs.

// In ExtendScript
var marker = new MarkerValue('');
marker.setParameters({ b: String(beatPos) }); // 1, 2, 3, or 4
layer.property('Marker').setValueAtTime(timeInSeconds, marker);

The Flicker Problem: Why 300 Markers Made AE Crash

The first version of the phase shift feature (which shifts the beat grid by one position) used the obvious approach: clear all markers, recreate them at new positions. For a song with 300 beats, this meant 300 remove operations followed by 300 add operations. After Effects redraws its audio waveform on every single marker operation. The result was visible flicker — a half-second freeze where the waveform blinked.

The solution: a differential update algorithm. Instead of clear+recreate, calculate which markers change state and only update those:

// Compute the diff between current and target state
const toRemove = currentMarkers.filter(m => !targetSet.has(m.getTime()));
const toAdd    = targetBeats.filter(t => !currentSet.has(t));
const toUpdate = currentMarkers.filter(m => targetSet.has(m.getTime()));

// Only remove/add the changed ones
toRemove.forEach(m => layer.property('Marker').removeKey(...));
toAdd.forEach(t => layer.property('Marker').setValueAtTime(t, newMarker));

// Update in-place via setValueAtKey — does NOT trigger waveform redraw
toUpdate.forEach(m => layer.property('Marker').setValueAtKey(keyIndex, newValue));

For a phase shift of ±1 step, this reduces marker operations from ~600 to approximately 150 — and more importantly, setValueAtKey updates a marker's color without triggering the waveform redraw. The flicker was gone.

💡 Pro Tip Beat positions 2 and 4 must use different AE label indices — not both Blue. The phase shift algorithm needs to distinguish them during recolor. BeatMarker uses Blue (8) for beat 2 and Aqua (3) for beat 4.

Shipping It: The One-Click Installer

The CEP install path being inside the AE application folder makes distribution complex. Users can't just unzip into a standard extensions folder. The BeatMarker AE installer is a .bat script that:

The result: a plugin that installs with one double-click, no manual registry editing, no ZIP extraction into obscure folders. For a tool built by a video editor, for video editors, that level of friction reduction matters enormously.

"If the install is confusing, nobody uses it. The plugin isn't done until installation is invisible."

The Short Version

CEP in After Effects 2026 works. It's just not documented anywhere that still applies. If you're building an AE plugin that needs real audio processing, full Node.js access, or Web Audio APIs — CEP is still the right choice and it ships production plugins today.

The plugin built from all of this: BeatMarker for After Effects — open source, free, and built entirely with Claude Code by a video editor who had never written a CEP panel in his life.