mcpField
C
creator-plugin-development

creator-plugin-development

github.com/lottiefilesDevelopment152 installsUpdated 3w ago
Repository

About creator-plugin-development

A development skill for building LottieFiles Creator plugins, aimed at developers working with its animation editor. It explains the split between a scene-manipulating sandbox and a React UI, with practical API and messaging guidance.

Key features

  1. 01Sandbox and UI architecture
  2. 02Scene, layer, and shape operations
  3. 03Keyframe animation patterns
  4. 04Asset import guidance
  5. 05Typed message passing
  6. 06Development and verification commands

Use cases

  1. 01Scaffolding a Creator plugin
  2. 02Creating or animating scene elements
  3. 03Importing SVG, image, or Lottie assets
  4. 04Debugging UI-to-plugin communication

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/lottiefiles/creator-plugin-skills --skill creator-plugin-development

After installing, type/creator-plugin-developmentin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
architecture-and-communication.md
importing-assets.md
network-and-libraries.md
scene-graph-and-nodes.md
shapes-styling-animation.md
storage-and-events.md

Details

From the project README

Creator Plugin Development

Creator Plugins extend the LottieFiles Creator animation application. They have a two-part sandboxed architecture:

  1. 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.
  2. 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.

Project Structure

my-plugin/
├── plugin/
│   ├── manifest.json          # Plugin metadata (id, name, apiVersion, entry, ui)
│   ├── plugin.ts              # Sandbox code — has `creator` API access
│   └── [helpers].ts           # Optional helper modules
├── src/
│   ├── main.tsx               # React DOM entry point
│   ├── app.tsx                # Main UI component
│   └── components/            # React components
├── vite.config.ts             # Uses @lottiefiles/vite-plugin-creator
├── tsconfig.json              # Root config with references
├── tsconfig.plugin.json       # Plugin sandbox TypeScript config (no DOM)
├── tsconfig.app.json          # UI TypeScript config (DOM + JSX)
├── index.html                 # Vite app template
└── package.json

tsconfig.plugin.json compiles sandbox code (no DOM libs). tsconfig.app.json compiles UI code (DOM + JSX).

The plugin manifest (plugin/manifest.json) defines the plugin's identity and entry points:

{
  "id": "unique-uuid-v4",
  "name": "My Plugin",
  "apiVersion": "1",
  "entry": "plugin.js",
  "ui": "ui.html"
}

Development Commands

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 pluginMessage
window.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:

// shared/types.ts
export type PluginMessage =
  | { type: 'create-shape'; color: string }
  | { type: 'import-svg'; content: string }
  | { type: 'delete-selection' };

For request/response tracking, include a messageId field.

Key API Patterns

Initialize Plugin
creator.ui.show({ width: 300, height: 500 });
Scene Access
const scene = creator.activeScene;
scene.size;        // { width, height }
scene.duration;    // seconds
scene.framerate;   // FPS
scene.layers;      // ReadonlyArray<Layer>
Create Shapes
const layer = creator.activeScene.createShapeLayer();
const rect = layer.createRectangle({ size: { width: 200, height: 150 } });
layer.createFill({ type: 'SOLID', color: { r: 66, g: 133, b: 244 } });
Import Assets
// From URL
const anim = await scene.import({ type: 'LOTTIE', url: 'https://...' });
const img = await scene.import({ type: 'IMAGE', url: 'https://...' });
const svg = await scene.import({ type: 'SVG', url: 'https://...' });

// From content string
const svgLayer = await scene.import({ type: 'SVG', content: svgString });

// Audio, from a URL or base64 content
const audio = await scene.import({ type: 'AUDIO', content: base64Audio });

LOTTIE and SVG imports return SceneLayer. IMAGE imports return ImageLayer. AUDIO imports return AudioLayer. A failed import rejects, so wrap scene.import() in try/catch.

Animate Properties
layer.position.addKeyframes([
  { frame: 0, value: { x: 100, y: 100 } },
  { frame: 60, value: { x: 400, y: 100 } },
]);

// With easing
const easeInOut = { type: 'CUBIC_BEZIER', x1: 0.42, y1: 0, x2: 0.58, y2: 1 };
layer.position.addKeyframes([
  { frame: 0, value: { x: 50, y: 100 }, easing: easeInOut },
  { frame: 60, value: { x: 350, y: 100 } },
]);
Selection
const selectedNodes = creator.selection.nodes;

creator.on('selection:nodes', (nodes) => {
  creator.ui.postMessage({ type: 'selection-changed', count: nodes.length });
});
Node Type Checking

Always check node types before operations, and skip types you do not support. scene.layers, creator.selection.nodes, selection:nodes, shapes and creator.assets can return types your plugin does not know about, including types newer than your installed API types. See "Unknown Node Types" in references/scene-graph-and-nodes.md:

const layers = creator.selection.nodes;
layers.forEach((node) => {
  if (node.type === 'SHAPE_LAYER') {
    // Shape layer operations (has .shapes, .fills, .strokes, .trimPaths)
  } else if (node.type === 'IMAGE_LAYER') {
    // Image layer operations (has .image)
  } else if (node.type === 'SCENE_LAYER') {
    // Scene layer operations (has .scene, .break())
  } else if (node.type === 'TEXT_LAYER') {
    // Text layer operations (has .text, .fontFamily, .fontStyle, .fontSize,
    // .alignment, .fill/.createFill, .stroke/.createStroke)
  } else if (node.type === 'NULL_LAYER') {
    // Null layer operations (transform only, has .layers)
  } else if (node.type === 'AUDIO_LAYER') {
    // Audio layer operations (has .audio, .muted, .volume; no transform)
  }
  // Any other type: skip it
});

Audio layers have no transform. Before reading position, opacity, or other transform members on a layer from scene.layers or the selection, check its type or use creator.utils.isTransformableLayer(layer). See references/scene-graph-and-nodes.md.

Network Requests

The plugin sandbox cannot make fetch requests. Use this pattern:

  1. UI fetches data from external API
  2. UI sends data to plugin via parent.postMessage({ pluginMessage: ... }, '*')
  3. Plugin processes data and applies to scene

For complete examples, see references/network-and-libraries.md.

Common Pitfalls

  1. Missing pluginMessage wrapper — UI-to-plugin messages MUST be wrapped: { pluginMessage: { ... } }. Plugin-to-UI messages do NOT need wrapping.
  2. Fetching from plugin sandbox — Network requests only work in UI code. Move fetch calls to src/.
  3. Using localStorage/sessionStorage — The sandboxed iframe blocks browser storage APIs. Use creator.clientStorage from plugin code instead.
  4. Not checking node types — Always verify node.type before accessing type-specific properties.
  5. Setting staticValue on animated properties — Setting staticValue when keyframes exist will not affect the animation. Clear keyframes first or modify keyframe values directly.
  6. Invisible shapes — Shapes need a fill or stroke to be visible. After createRectangle(), call createFill().
  7. Scale values are percentages — 100 = 100% scale (not 1.0). Use { x: 100, y: 100 } for normal size.
  8. Opacity is 0-100 — Not 0-1. Use 100 for fully opaque.
  9. Color values are 0-255 — RGB channels use the range { r: 0-255, g: 0-255, b: 0-255 }.
  10. Not calling creator.ui.show() early — Call it at the top of plugin.ts, before setting up message handlers.
  11. 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.
  12. Assuming every layer has a transform — Audio layers have no visible, position, scale, rotation, or opacity. Code that walks scene.layers and reads those members fails to compile, and throws at runtime on a project with audio. Filter by type first, and skip types you do not handle.

Verification Checklist

Before considering a task complete:

  • Run npm exec tsc -- -b — fix all type errors
  • Test message flow: UI sends > plugin receives > plugin responds > UI receives
  • Confirm network requests are made from UI code, not plugin sandbox
  • Verify pluginMessage wrapping is correct in both directions

Reference Guide

For deeper information, consult these reference files as needed:

ReferenceWhen to Consult
references/architecture-and-communication.mdDetailed architecture, complete message passing examples, UI API
references/scene-graph-and-nodes.mdScene hierarchy, node types, traversal patterns
references/shapes-styling-animation.mdCreating shapes, fills/strokes/gradients, keyframes, easing
references/importing-assets.mdLOTTIE/SVG/IMAGE/AUDIO import formats and patterns
references/storage-and-events.mdclientStorage, node data, selection events, timeline API
references/network-and-libraries.mdFetch-from-UI pattern, using npm packages

Comments

Markdown supported