← Back to list

figma-plugin
by srstomp
An orchestration plugin for Claude Code that enables reliable autonomous development sessions with configurable checkpoints and task management.
⭐ 0🍴 0📅 Jan 23, 2026
SKILL.md
name: figma-plugin description: Create Figma plugins with the Plugin API. Covers plugin architecture (sandbox + UI), node manipulation, styles, components, UI development with postMessage communication, and publishing. Use this skill when building Figma plugins, working with the Figma Plugin API, or creating design automation tools.
Figma Plugin Development
Build plugins that extend Figma's functionality.
Plugin Architecture
┌─────────────────────────────────────────────────────────────┐
│ FIGMA APPLICATION │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ ┌─────────────────────────┐ │
│ │ MAIN THREAD │ │ UI THREAD │ │
│ │ (Sandbox) │ │ (iframe) │ │
│ │ │ │ │ │
│ │ • Plugin API │ ←──→ │ • HTML/CSS/JS │ │
│ │ • Node access │ post │ • User interface │ │
│ │ • figma.* │ Msg │ • No Figma API access │ │
│ │ • Read/write doc │ │ • Can use npm packages │ │
│ │ │ │ │ │
│ └─────────────────────┘ └─────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Key Constraints:
- Main thread (sandbox): Access to Figma API, but limited JS environment
- UI thread (iframe): Full browser environment, but no Figma API access
- Communication via
postMessage/onmessage
Plugin Types
| Type | Use Case | Has UI |
|---|---|---|
| Design Plugin | Manipulate design files | Optional |
| FigJam Plugin | Manipulate FigJam files | Optional |
| Widget | Interactive objects in canvas | Always (React-like) |
Quick Start
Minimal Plugin (No UI)
// code.ts
// Runs in main thread with Figma API access
// Get current selection
const selection = figma.currentPage.selection;
if (selection.length === 0) {
figma.notify('Please select something');
figma.closePlugin();
}
// Do something with selection
for (const node of selection) {
if ('fills' in node) {
node.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }];
}
}
figma.notify(`Updated ${selection.length} items`);
figma.closePlugin();
Plugin with UI
// code.ts (main thread)
figma.showUI(__html__, { width: 300, height: 200 });
// Receive messages from UI
figma.ui.onmessage = (msg) => {
if (msg.type === 'create-rectangle') {
const rect = figma.createRectangle();
rect.resize(msg.width, msg.height);
rect.fills = [{ type: 'SOLID', color: { r: 0.5, g: 0.5, b: 1 } }];
figma.currentPage.appendChild(rect);
figma.viewport.scrollAndZoomIntoView([rect]);
}
if (msg.type === 'cancel') {
figma.closePlugin();
}
};
<!-- ui.html (UI thread) -->
<div id="app">
<label>Width: <input id="width" type="number" value="100"></label>
<label>Height: <input id="height" type="number" value="100"></label>
<button id="create">Create Rectangle</button>
<button id="cancel">Cancel</button>
</div>
<script>
document.getElementById('create').onclick = () => {
parent.postMessage({
pluginMessage: {
type: 'create-rectangle',
width: Number(document.getElementById('width').value),
height: Number(document.getElementById('height').value),
}
}, '*');
};
document.getElementById('cancel').onclick = () => {
parent.postMessage({ pluginMessage: { type: 'cancel' } }, '*');
};
</script>
Core Concepts
Node Types
| Category | Types |
|---|---|
| Containers | PageNode, FrameNode, GroupNode, SectionNode |
| Shapes | RectangleNode, EllipseNode, PolygonNode, StarNode, LineNode, VectorNode |
| Text | TextNode |
| Components | ComponentNode, ComponentSetNode, InstanceNode |
| Media | ImageNode (via fills) |
| Special | BooleanOperationNode, SliceNode, ConnectorNode |
Properties by Mixin
// Figma uses mixins for shared properties
// BlendMixin - opacity, blendMode, effects
node.opacity = 0.5;
node.effects = [{ type: 'DROP_SHADOW', ... }];
// GeometryMixin - fills, strokes
node.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }];
node.strokes = [{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }];
node.strokeWeight = 2;
// LayoutMixin - position, size, constraints
node.x = 100;
node.y = 100;
node.resize(200, 100);
node.constraints = { horizontal: 'SCALE', vertical: 'CENTER' };
// ChildrenMixin - parent/child relationships
node.children; // readonly
node.appendChild(child);
node.insertChild(0, child);
Type Guards
// Check node type before accessing properties
function isTextNode(node: SceneNode): node is TextNode {
return node.type === 'TEXT';
}
function hasChildren(node: SceneNode): node is FrameNode | GroupNode {
return 'children' in node;
}
function hasFills(node: SceneNode): node is GeometryMixin & SceneNode {
return 'fills' in node;
}
// Usage
for (const node of figma.currentPage.selection) {
if (isTextNode(node)) {
node.characters = 'Updated text';
}
if (hasFills(node)) {
node.fills = [...];
}
}
Communication Pattern
Main → UI
// code.ts
figma.ui.postMessage({ type: 'selection-changed', count: selection.length });
// ui.ts
window.onmessage = (event) => {
const msg = event.data.pluginMessage;
if (msg.type === 'selection-changed') {
document.getElementById('count').textContent = msg.count;
}
};
UI → Main
// ui.ts
parent.postMessage({ pluginMessage: { type: 'do-something', data: 'value' } }, '*');
// code.ts
figma.ui.onmessage = (msg) => {
if (msg.type === 'do-something') {
console.log(msg.data);
}
};
Typed Messages
// shared-types.ts
type PluginMessage =
| { type: 'create-shape'; shape: 'rectangle' | 'ellipse'; size: number }
| { type: 'update-selection'; color: RGB }
| { type: 'cancel' };
type UIMessage =
| { type: 'selection-changed'; count: number; types: string[] }
| { type: 'error'; message: string };
// code.ts
figma.ui.onmessage = (msg: PluginMessage) => {
switch (msg.type) {
case 'create-shape':
// TypeScript knows msg has shape and size
break;
case 'update-selection':
// TypeScript knows msg has color
break;
}
};
Common Operations
Create Nodes
// Shapes
const rect = figma.createRectangle();
const ellipse = figma.createEllipse();
const frame = figma.createFrame();
// Text (must load font first)
const text = figma.createText();
await figma.loadFontAsync({ family: 'Inter', style: 'Regular' });
text.characters = 'Hello World';
// Component
const component = figma.createComponent();
component.name = 'Button';
// Instance
const instance = component.createInstance();
Modify Selection
// Get selection
const selection = figma.currentPage.selection;
// Set selection
figma.currentPage.selection = [node1, node2];
// Listen for changes
figma.on('selectionchange', () => {
const newSelection = figma.currentPage.selection;
figma.ui.postMessage({ type: 'selection', nodes: newSelection.map(n => n.name) });
});
Colors
// RGB (0-1 range, not 0-255)
const red: RGB = { r: 1, g: 0, b: 0 };
const blue: RGB = { r: 0, g: 0, b: 1 };
// With alpha
const semiTransparent: RGBA = { r: 1, g: 0, b: 0, a: 0.5 };
// Hex to RGB helper
function hexToRgb(hex: string): RGB {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16) / 255,
g: parseInt(result[2], 16) / 255,
b: parseInt(result[3], 16) / 255,
} : { r: 0, g: 0, b: 0 };
}
// Apply solid fill
node.fills = [{ type: 'SOLID', color: hexToRgb('#FF5733') }];
Storage
// Per-document storage
await figma.clientStorage.setAsync('key', { data: 'value' });
const data = await figma.clientStorage.getAsync('key');
// Per-node storage (survives copy/paste)
node.setPluginData('key', JSON.stringify({ saved: true }));
const nodeData = JSON.parse(node.getPluginData('key') || '{}');
// Shared across plugins (use namespace)
node.setSharedPluginData('com.myplugin', 'key', 'value');
Anti-Patterns
❌ Forgetting Async Font Loading
// WRONG: Will fail
const text = figma.createText();
text.characters = 'Hello'; // Error: font not loaded
// RIGHT
const text = figma.createText();
await figma.loadFontAsync({ family: 'Inter', style: 'Regular' });
text.characters = 'Hello';
❌ Modifying Readonly Arrays
// WRONG: fills is readonly
node.fills.push(newFill); // Error
// RIGHT: Create new array
node.fills = [...node.fills, newFill];
❌ Not Handling Selection Empty
// WRONG: Crashes if nothing selected
const node = figma.currentPage.selection[0];
node.name = 'Renamed'; // Error if selection empty
// RIGHT
const selection = figma.currentPage.selection;
if (selection.length === 0) {
figma.notify('Please select something');
return;
}
❌ Blocking Main Thread
// WRONG: Long loop blocks Figma
for (let i = 0; i < 10000; i++) {
figma.createRectangle();
}
// RIGHT: Batch with setTimeout
async function createManyRectangles(count: number) {
const batchSize = 100;
for (let i = 0; i < count; i += batchSize) {
for (let j = 0; j < Math.min(batchSize, count - i); j++) {
figma.createRectangle();
}
// Yield to Figma
await new Promise(resolve => setTimeout(resolve, 0));
}
}
❌ Not Closing Plugin
// WRONG: Plugin hangs
figma.showUI(__html__);
// User can't close if no cancel button
// RIGHT: Always provide exit
figma.showUI(__html__);
figma.ui.onmessage = (msg) => {
if (msg.type === 'close') {
figma.closePlugin();
}
};
Plugin Categories
| Category | Examples | Key APIs |
|---|---|---|
| Generators | Lorem ipsum, icons, patterns | figma.create*, fills, text |
| Utilities | Rename layers, organize, cleanup | Selection, traversal, properties |
| Importers | JSON to layers, spreadsheet data | figma.create*, positioning |
| Exporters | Design tokens, code generation | Traversal, styles, properties |
| Connectors | Sync with external tools | figma.clientStorage, fetch |
| Accessibility | Contrast checker, a11y audit | Colors, text, traversal |
References:
- references/plugin-api.md — Complete API reference for nodes, properties, and methods
- references/ui-development.md — Building plugin UIs with HTML/CSS/JS or React
- references/common-patterns.md — Selection, traversal, batch operations, undo
- references/project-setup.md — Manifest, TypeScript, bundling, publishing
Score
Total Score
60/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
○LICENSE
ライセンスが設定されている
0/10
✓説明文
100文字以上の説明がある
+10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
Reviews
💬
Reviews coming soon