creator-plugin-development

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Creator Plugin Development

Creator Plugin 开发

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.
Creator Plugin 用于扩展 LottieFiles Creator 动画应用。它采用双部分 sandbox 架构:
  1. Plugin Sandbox (
    plugin/plugin.ts
    ) — 在隔离环境中运行,可访问
    creator
    全局 API。可以操作 scene、layer、shape 和 keyframe。无法发起网络请求。
  2. UI (
    src/
    ) — 在 iframe 中渲染的标准 React 应用。可以通过
    fetch
    发起网络请求。无法访问
    creator
    API。
这两部分仅通过消息传递进行通信。

Project Structure

项目结构

text
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:
json
{
  "id": "unique-uuid-v4",
  "name": "My Plugin",
  "apiVersion": "1",
  "entry": "plugin.js",
  "ui": "ui.html"
}
text
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
用于编译 sandbox 代码(不包含 DOM libs)。
tsconfig.app.json
用于编译 UI 代码(DOM + JSX)。
Plugin manifest(
plugin/manifest.json
)定义 Plugin 的标识信息和入口点:
json
{
  "id": "unique-uuid-v4",
  "name": "My Plugin",
  "apiVersion": "1",
  "entry": "plugin.js",
  "ui": "ui.html"
}

Development Commands

开发命令

bash
npm create @lottiefiles/creator-plugin@latest my-plugin  # Scaffold a new plugin
bash
npm create @lottiefiles/creator-plugin@latest my-plugin  # Scaffold a new plugin

From the plugin directory (e.g., plugins/my-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`.
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)

在 Creator 中加载:**Plugins > Develop > New plugin** > 输入 `npm run dev` 输出的 localhost URL。

Communication Pattern (Critical)

通信模式(关键)

This is the most common source of bugs. The message wrapping is asymmetric:
这是最常见的 bug 来源。消息包装方式是不对称的:

UI to Plugin

UI 到 Plugin

typescript
// In UI code (src/app.tsx) — MUST wrap in pluginMessage object
parent.postMessage(
  { pluginMessage: { type: 'create-shape', color: '#ff0000' } },
  '*'
);
typescript
// In UI code (src/app.tsx) — MUST wrap in pluginMessage object
parent.postMessage(
  { pluginMessage: { type: 'create-shape', color: '#ff0000' } },
  '*'
);

Plugin Receives Message

Plugin 接收消息

typescript
// In plugin sandbox (plugin/plugin.ts) — messages arrive unwrapped
creator.ui.onMessage((msg) => {
  if (msg.type === 'create-shape') {
    // Use creator API here
  }
});
typescript
// 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

Plugin 到 UI

typescript
// In plugin sandbox — no wrapping needed
creator.ui.postMessage({ type: 'shape-created', layerId: layer.id });
typescript
// In plugin sandbox — no wrapping needed
creator.ui.postMessage({ type: 'shape-created', layerId: layer.id });

UI Receives Message

UI 接收消息

typescript
// In UI code — messages arrive wrapped in pluginMessage
window.addEventListener('message', (event) => {
  const message = event.data.pluginMessage;
  if (message?.type === 'shape-created') {
    // Handle response
  }
});
typescript
// 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:
typescript
// 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.
定义共享消息类型,以在编译时发现不匹配问题:
typescript
// shared/types.ts
export type PluginMessage =
  | { type: 'create-shape'; color: string }
  | { type: 'import-svg'; content: string }
  | { type: 'delete-selection' };
如需跟踪请求/响应,请加入
messageId
字段。

Key API Patterns

关键 API 模式

Initialize Plugin

初始化 Plugin

typescript
creator.ui.show({ width: 300, height: 500 });
typescript
creator.ui.show({ width: 300, height: 500 });

Scene Access

Scene 访问

typescript
const scene = creator.activeScene;
scene.size;        // { width, height }
scene.duration;    // seconds
scene.framerate;   // FPS
scene.layers;      // ReadonlyArray<Layer>
typescript
const scene = creator.activeScene;
scene.size;        // { width, height }
scene.duration;    // seconds
scene.framerate;   // FPS
scene.layers;      // ReadonlyArray<Layer>

Create Shapes

创建 Shape

typescript
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 } });
typescript
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

导入资源

typescript
// 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 });
LOTTIE and SVG imports return
SceneLayer
. IMAGE imports return
ImageLayer
.
typescript
// 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 });
LOTTIE 和 SVG 导入返回
SceneLayer
。IMAGE 导入返回
ImageLayer
。

Animate Properties

动画属性

typescript
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 } },
]);
typescript
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

选择

typescript
const selectedNodes = creator.selection.nodes;

creator.on('selection:nodes', (nodes) => {
  creator.ui.postMessage({ type: 'selection-changed', count: nodes.length });
});
typescript
const selectedNodes = creator.selection.nodes;

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

Node Type Checking

节点类型检查

Always verify node types before operations:
typescript
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)
  }
});
操作前始终验证节点类型:
typescript
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)
  }
});

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
.
Plugin sandbox 无法发起
fetch
请求。请使用以下模式:
  1. UI 从外部 API 获取数据
  2. UI 通过
    parent.postMessage({ pluginMessage: ... }, '*')
    将
    数据发送给 Plugin
  3. Plugin 处理数据并将其应用到 scene
完整示例请参阅
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.
  1. 缺少
    pluginMessage
    包装
    — 从 UI 发往 Plugin 的消息必须包装为:
    { pluginMessage: { ... } }
    。从 Plugin 发往 UI 的消息则不需要包装。
  2. 在 Plugin sandbox 中发起 fetch — 网络请求只能在 UI 代码中执行。将
    fetch
    调用移到
    src/
    。
  3. 使用
    localStorage
    /
    sessionStorage
    — sandbox iframe 会阻止浏览器 storage API。请改用 Plugin 代码中的
    creator.clientStorage
    。
  4. 未检查节点类型 — 访问类型专属属性前,务必验证
    node.type
    。
  5. 在已动画化的属性上设置
    staticValue
    — 存在 keyframe 时设置
    staticValue
    不会影响动画。请先清除 keyframe,或直接修改 keyframe 的值。
  6. 不可见的 Shape — Shape 必须包含 fill 或 stroke 才会可见。调用
    createRectangle()
    后,调用
    createFill()
    。
  7. 缩放值是百分比 —
    100
    = 100% 缩放(不是
    1.0
    )。正常大小使用
    { x: 100, y: 100 }
    。
  8. 不透明度范围为 0-100 — 不是 0-1。完全不透明使用
    100
    。
  9. 颜色值为 0-255 — RGB 通道范围为
    { r: 0-255, g: 0-255, b: 0-255 }
    。
  10. 未尽早调用
    creator.ui.show()
    — 在设置 message handler 之前,先在
    plugin.ts
    顶部调用它。
  11. 在 UI 就绪前发送消息 — 调用
    creator.ui.show()
    后立即调用
    creator.ui.postMessage()
    ,消息会被丢弃,因为 iframe 尚未加载。请使用“ui-ready”握手:UI 在 mount 时发送
    { type: 'ui-ready' }
    ,Plugin 仅在收到该消息后才发送数据。

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
在将任务视为完成之前:
  • 运行
    npm exec tsc -- -b
    — 修复所有 type error
  • 测试消息流:UI 发送 > Plugin 接收 > Plugin 响应 > UI 接收
  • 确认网络请求由 UI 代码发起,而不是 Plugin sandbox
  • 验证两个方向的
    pluginMessage
    包装均正确

Reference Guide

参考指南

For deeper information, consult these reference files as needed:
ReferenceWhen to Consult
references/architecture-and-communication.md
Detailed architecture, complete message passing examples, UI API
references/scene-graph-and-nodes.md
Scene hierarchy, node types, traversal patterns
references/shapes-styling-animation.md
Creating shapes, fills/strokes/gradients, keyframes, easing
references/importing-assets.md
LOTTIE/SVG/IMAGE import formats and patterns
references/storage-and-events.md
clientStorage, node data, selection events, timeline API
references/network-and-libraries.md
Fetch-from-UI pattern, using npm packages
如需了解更深入的信息,请根据需要查阅以下参考文件:
参考资料何时查阅
references/architecture-and-communication.md
详细架构、完整消息传递示例、UI API
references/scene-graph-and-nodes.md
Scene 层级、节点类型、遍历模式
references/shapes-styling-animation.md
创建 shape、填充/描边/渐变、keyframe、缓动
references/importing-assets.md
LOTTIE/SVG/IMAGE 导入格式和模式
references/storage-and-events.md
clientStorage、节点数据、选择事件、timeline API
references/network-and-libraries.md
从 UI 发起 fetch 的模式、使用 npm 包