Use when creating or modifying LottieFiles Creator Plugins with the Creator Plugin API.
Covers plugin sandbox/UI architecture, message passing, scene graph manipulation, assets,
storage, animation, and plugin development workflows.
Use when creating or modifying LottieFiles Creator Plugins with the Creator Plugin API.
Covers plugin sandbox/UI architecture, message passing, scene graph manipulation, assets,
storage, animation, and plugin development workflows.
Creator Plugin Development
Creator Plugins extend the LottieFiles Creator animation application. They have a two-part sandboxed architecture:
Plugin Sandbox (plugin/plugin.ts) — Runs in isolation with access to the creator global API. Can manipulate scenes, layers, shapes, keyframes. Cannot make network requests.
UI (src/) — Standard React application rendered in an iframe. Can make network requests via fetch. Cannot access the creator API.
The two parts communicate exclusively via message passing.
npm create @lottiefiles/creator-plugin@latest my-plugin # Scaffold a new plugin# From the plugin directory (e.g., plugins/my-plugin/):
npm run dev # Start dev server with HTTPS hot-reload
npm run build # TypeScript check + Vite production build
npm exec tsc -- -b # Type check (run before completing any task)
To load in Creator: Plugins > Develop > New plugin > enter the localhost URL from npm run dev.
Communication Pattern (Critical)
This is the most common source of bugs. The message wrapping is asymmetric:
UI to Plugin
// In UI code (src/app.tsx) — MUST wrap in pluginMessage object
parent.postMessage(
{ pluginMessage: { type: 'create-shape', color: '#ff0000' } },
'*'
);
Plugin Receives Message
// In plugin sandbox (plugin/plugin.ts) — messages arrive unwrapped
creator.ui.onMessage((msg) => {
if (msg.type === 'create-shape') {
// Use creator API here
}
});
Plugin to UI
// In plugin sandbox — no wrapping needed
creator.ui.postMessage({ type: 'shape-created', layerId: layer.id });
UI Receives Message
// In UI code — messages arrive wrapped in pluginMessagewindow.addEventListener('message', (event) => {
const message = event.data.pluginMessage;
if (message?.type === 'shape-created') {
// Handle response
}
});
Type-Safe Messages
Define shared message types to catch mismatches at compile time:
The plugin sandbox cannot make fetch requests. Use this pattern:
UI fetches data from external API
UI sends data to plugin via parent.postMessage({ pluginMessage: ... }, '*')
Plugin processes data and applies to scene
For complete examples, see references/network-and-libraries.md.
Common Pitfalls
Missing pluginMessage wrapper — UI-to-plugin messages MUST be wrapped: { pluginMessage: { ... } }. Plugin-to-UI messages do NOT need wrapping.
Fetching from plugin sandbox — Network requests only work in UI code. Move fetch calls to src/.
Using localStorage/sessionStorage — The sandboxed iframe blocks browser storage APIs. Use creator.clientStorage from plugin code instead.
Not checking node types — Always verify node.type before accessing type-specific properties.
Setting staticValue on animated properties — Setting staticValue when keyframes exist will not affect the animation. Clear keyframes first or modify keyframe values directly.
Invisible shapes — Shapes need a fill or stroke to be visible. After createRectangle(), call createFill().
Scale values are percentages — 100 = 100% scale (not 1.0). Use { x: 100, y: 100 } for normal size.
Opacity is 0-100 — Not 0-1. Use 100 for fully opaque.
Color values are 0-255 — RGB channels use the range { r: 0-255, g: 0-255, b: 0-255 }.
Not calling creator.ui.show() early — Call it at the top of plugin.ts, before setting up message handlers.
Sending messages before UI is ready — creator.ui.postMessage() right after creator.ui.show() will be dropped because the iframe hasn't loaded. Use a "ui-ready" handshake: have the UI send { type: 'ui-ready' } on mount, then send data from the plugin only after receiving that message.