Plugins
A plugin is a package — a plugin.json manifest plus an ES module —
installed from a .zip or a folder.
Installing
Window → Plugins… opens the manager. Choose a .zip or a folder.
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.
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.
Permissions
| Permission | The plugin can |
|---|---|
scene:read | See layer names, structure and scalar properties |
scene:write | Create, change and delete layers |
animation:read | Read keyframes and sample animated values |
animation:write | Create and change keyframes and expressions |
timeline | Read the current time and move the playhead |
Registering commands, showing notifications, opening the plugin’s own panel and reading composition settings need no permission — none of those read project data or change it.
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 |
| 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.
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 |
| A panel | The Plugins dock panel, tabbing alongside Effects and Graph |
| Notifications | Toasts, always prefixed with the plugin’s name |
| The package | Plugins → Manage Plugins… — status, permissions, enable/disable, uninstall |