Plugins
A plugin is a package — a plugin.json manifest plus an ES module —
installed from a .zip or a folder, or from the registry.
Plugins are a hosted-build feature. In the open-source local edition
(VITE_EDITION=local) the Plugins panel, the menu and the host itself are
absent, and the registry is never contacted — a self-hosted editor makes no
request to premation.com on account of plugins, ever.
This is not a stripped binary with a hidden switch, and it is deliberate: the registry, the review queue, the reporting path and the signed revocation list are the things that make running a stranger’s code in your editor defensible, and a build with none of them should not be offering the sandbox either.
A project containing plugin content still opens on a local build, keeps its custom layers, plugin effects and generated subtrees exactly as written, and saves back to the same bytes. Losslessness is the invariant — a build that quietly dropped what it could not run would turn “open this to look at it” into data loss.
Installing
Plugins ▸ Manage Plugins… opens the manager. From there:
The Add plugin button opens a menu:
| Menu item | What happens |
|---|---|
| Install from a package… | Picks a .zip or .mplugin you were sent or built yourself |
| Install from a folder… | An unpacked folder — the one to use while developing |
| Download starter template | Writes out a complete working plugin: a command, a keyframe write and a panel |
| Download effect sample (blur) | A second sample covering the half the starter does not — a two-pass Gaussian blur, as a real multi-pass GPU effect |
Browsing the registry is the other route in, and picking a plugin there downloads it, verifies the signature, then sends it through the same package reader and the same consent screen a local file gets. A registry install is not a shortcut past the permission screen.
Before any code exists anywhere, the manifest is validated and a consent screen shows you what the plugin is asking to be allowed to do. Only after you accept does the entry module get loaded into its worker.
Installing registers the plugin’s commands immediately — no restart — and they appear in the command palette and the Plugins menu right away. Installs persist across reloads. Packages are signed, and the package’s digest is pinned in the registry’s metadata before the bytes move, so what arrives is checked against what was published rather than against itself.
Permissions
| Permission | The plugin can |
|---|---|
scene:read | See layer names, structure and scalar properties |
scene:proxy | Write only inside its own layer type’s generated subtree |
scene:write | Create, change and delete layers anywhere |
animation:read | Read keyframes and sample animated values |
animation:write | Create and change keyframes and expressions |
assets:read | Read the pixels of images already in the composition |
assets:write | Create images and place them as layers |
net:fetch | Contact the hosts listed in the manifest — and only those |
timeline | Read the current time and move the playhead |
Registering commands, showing notifications, opening the plugin’s own panel, reading composition settings and using the plugin’s own storage need no permission — none of those read project data or change it.
scene:proxy is the one to look for on a generator. A plugin that builds
a subtree under its own layer only ever touches its own children, but before
this permission existed it had to ask for scene:write — and that consent
screen reads “create, change, delete and reparent layers”, which is
indistinguishable from a plugin that could rearrange your whole project. A
generator asking for scene:read + scene:proxy is asking for the narrow
thing.
Consent is per permission, not one yes over the list. The install screen ticks everything the manifest asks for and you may untick any of it. You can change your mind later — Manage Plugins… → Permissions on the row — which restarts the plugin with the new set. A grant is always intersected with the manifest, so nothing can end up with more than it disclosed.
A refused call returns an error naming the missing permission rather than silently doing nothing, so a well-written plugin degrades deliberately instead of behaving strangely.
Why this is safe
Each plugin runs in a Worker sandbox that locks down its own globals before importing the plugin’s code.
| Failure mode | What actually happens |
|---|---|
| Plugin loops forever | The worker stops answering a heartbeat and is terminated in about 12 seconds. The editor is untouched |
| Plugin tries to read your account token or AI keys | There is no localStorage in a worker realm |
| Plugin tries to phone home | fetch, XMLHttpRequest, WebSocket and importScripts are replaced with throwing stubs before the plugin’s module is imported. A plugin that legitimately needs the network declares its hosts and asks for net:fetch — see Network access |
| Plugin tries to read the interface or forge clicks | No document, no window |
| Plugin deletes your project | It needs scene:write, and it is one Ctrl/⌘ + Z |
Every host API call executes inside the command system, so anything a plugin changes is a single undo.
Panels
A plugin’s UI panel runs in a frame sandboxed without same-origin access, so it has an opaque origin and cannot read the editor’s document, cookies or storage. Its only way out is a message to its own plugin — not to the editor, and not to another plugin.
Panels have no network: inline scripts run, fetch does not, and a remote
image is refused.
This design exists because downloading plugins from strangers is the normal distribution model for creative tools. “The user chose the file” is not a security control.
What a plugin can contribute
Four kinds of thing, all declared in the manifest and loaded lazily.
Commands and panels
The originals: a named command that appears in the Plugins menu and the command palette, and a panel that docks alongside Effects and Graph.
Layer types
A plugin can contribute its own layer type, which behaves like a built-in one in the layer tree and the timeline and owns a generated subtree of ordinary layers beneath it. That subtree is what a project without the plugin still carries — the layers are real, so the file opens and renders; only the ability to regenerate them is missing.
The distinction that matters when using one: an edit you make by hand is preserved, and a regeneration replaces what the plugin generated. A plugin is told about the first and not the second, so hand-editing a generated child is supported rather than fought.
Effects
A plugin effect declares typed parameters and supplies a shader body — the host writes the pipeline, the uniform layout and the draw call. It is narrower than a native After Effects plugin on purpose: there is no JavaScript in the frame loop, so a plugin cannot make the renderer stutter, and the parameter layout is validated against a real GPU rather than trusted.
Plugin effects appear in their own Plugins folder in the Effects browser, labelled with the plugin’s name — two plugins may both ship a “Glow”.
A plugin effect needs the WebGPU tier. On the WebGL2 fallback it renders
its input unchanged: not degraded, inert. A well-written plugin declares
webgpu as required so it refuses to install rather than looking healthy and
doing nothing — and the editor says which tier it is on in the viewport
header.
Storage
Per-plugin storage, both global and per-project, needing no permission. It is the plugin’s own space — it cannot read another plugin’s, and it is not your project data.
Network access
A plugin that needs the network lists the exact hosts in its manifest and
asks for net:fetch. It gets those hosts and nothing else.
| The design decision | Why |
|---|---|
| Hosts are declared, and exact | A wildcard is a permission you cannot read on a consent screen |
| The request is made by the main process | Not by the Worker and not by the panel, so there is no origin to abuse and no cookie jar to reach |
| The panel stays network-free | Inline scripts run, fetch does not, a remote image is refused |
| DNS rebinding is handled | A host that resolves to something else between the check and the request is the interesting attack, and it is the one a naive allowlist misses |
The consent screen says the dangerous part out loud rather than listing a domain and hoping you infer it.
Supervision
| Mechanism | Behaviour |
|---|---|
| Boot timeout | 8 seconds to activate, then stopped with a reason |
| Heartbeat | Pinged every 4 seconds; two unanswered pings terminate it |
| Errors | Surfaced, never swallowed — a fatal shows in the manager row with a Restart button, and as a toast prefixed with the plugin’s name |
| Log | Per plugin. Carries its own console output, every call the permission gate refused, and the crash that stopped it. Kept after the plugin dies, because that is when it gets read |
A plugin that is installed but not running still appears in the menu, disabled, saying why. Nothing an installed plugin does is invisible.
Where a plugin shows up
| Contribution | Where you find it |
|---|---|
| Registered commands | The Plugins menu under the plugin’s name, and the command palette (Ctrl/⌘ + Shift + P) |
| A panel | Wherever the manifest’s placement sends it, plus a Your plugin: Panel command |
| A layer type | The layer-creation menu — Layer ▸ New |
| An effect | A folder named after the plugin, in the Effects browser |
| Notifications | Toasts, always prefixed with the plugin’s name |
| The package | Plugins ▸ Manage Plugins… — status, permissions, enable/disable, uninstall |
A plugin’s row also carries, when they apply: whether its effects can draw on this renderer, a takedown notice (one marked malicious cannot be dismissed unread), and its security events — every signing-key rotation it has been through and how each one was authorised.
Nothing you click closes a plugin surface, and there is no ✕ on a plugin panel. A close button would mean “hide this until something else brings it back”, which is not a state you can reason about. Disabling or uninstalling the plugin is what removes it, and both are reversible from one place.
Disable is not uninstall. Disabling terminates the worker and unregisters the plugin’s commands but keeps the package. A panel never outlives its worker, so stopping a plugin closes its panel — a frame left on screen with nothing answering it reads as the editor being broken.
The registry
Browsing, installing and update checks go through the hosted registry. Publishers are namespaced, packages are scanned at publish time, and a reviewer queue sits behind the listings. You can browse it in the app, or on the web at premation.com/plugins — the listing is public so a plugin can be found, linked and evaluated before anyone signs up.
Building and publishing your own is its own guide: Publishing a plugin.
Updates
Checked only when you open the manager — never on a timer, never in the background. That is the editor asking the registry on the one screen where the answer is the point; it is not a plugin reaching anywhere. A failed check is silent, so working offline does not produce errors.
An update that asks for more permissions than you granted goes back through the consent screen instead of installing quietly.
When a plugin is withdrawn
Revocation is signed and fetched by the editor, so a plugin that turns out to be malicious stops being trusted on machines that already have it, rather than only disappearing from the listings.
You are told which plugin, and why, in the manager row — not silently left with something disabled. Your project keeps its content either way; the plugin is what stops running, not the layers it made.
Reporting one
Every plugin has a reporting path from its row in the manager. A report goes to the reviewer queue, which is a person, not a filter.