figma-code-agent 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -0
- package/bin/install.js +328 -0
- package/knowledge/README.md +62 -0
- package/knowledge/css-strategy.md +973 -0
- package/knowledge/design-to-code-assets.md +855 -0
- package/knowledge/design-to-code-layout.md +929 -0
- package/knowledge/design-to-code-semantic.md +1085 -0
- package/knowledge/design-to-code-typography.md +1003 -0
- package/knowledge/design-to-code-visual.md +1145 -0
- package/knowledge/design-tokens-variables.md +1261 -0
- package/knowledge/design-tokens.md +960 -0
- package/knowledge/figma-api-devmode.md +894 -0
- package/knowledge/figma-api-plugin.md +920 -0
- package/knowledge/figma-api-rest.md +742 -0
- package/knowledge/figma-api-variables.md +848 -0
- package/knowledge/figma-api-webhooks.md +876 -0
- package/knowledge/payload-blocks.md +1184 -0
- package/knowledge/payload-figma-mapping.md +1210 -0
- package/knowledge/payload-visual-builder.md +1004 -0
- package/knowledge/plugin-architecture.md +1176 -0
- package/knowledge/plugin-best-practices.md +1206 -0
- package/knowledge/plugin-codegen.md +1313 -0
- package/package.json +31 -0
- package/skills/README.md +103 -0
- package/skills/audit-plugin/SKILL.md +244 -0
- package/skills/build-codegen-plugin/SKILL.md +279 -0
- package/skills/build-importer/SKILL.md +320 -0
- package/skills/build-plugin/SKILL.md +199 -0
- package/skills/build-token-pipeline/SKILL.md +363 -0
- package/skills/ref-html/SKILL.md +290 -0
- package/skills/ref-layout/SKILL.md +150 -0
- package/skills/ref-payload-block/SKILL.md +415 -0
- package/skills/ref-react/SKILL.md +222 -0
- package/skills/ref-tokens/SKILL.md +347 -0
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
# Figma Plugin API Reference
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Authoritative reference for the Figma Plugin API covering the sandbox execution model, SceneNode type hierarchy, IPC messaging patterns, standard plugin manifest configuration, key API methods, and plugin constraints. This module covers **standard plugins** (design-mode plugins with optional UI). For Dev Mode codegen plugins, see `figma-api-devmode.md`.
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
Reference this module when you need to:
|
|
10
|
+
|
|
11
|
+
- Understand the plugin sandbox model (main thread vs UI iframe)
|
|
12
|
+
- Build a standard Figma plugin with a UI
|
|
13
|
+
- Navigate the SceneNode type hierarchy in plugin code
|
|
14
|
+
- Implement IPC (inter-process communication) between plugin main thread and UI
|
|
15
|
+
- Configure `manifest.json` for a standard plugin
|
|
16
|
+
- Access node properties, traverse the document tree, or create nodes
|
|
17
|
+
- Load fonts, export images, or store plugin data
|
|
18
|
+
- Understand plugin execution constraints and limitations
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Content
|
|
23
|
+
|
|
24
|
+
### Plugin Architecture: Sandbox Model
|
|
25
|
+
|
|
26
|
+
Figma plugins use a **two-environment execution model** for security and stability.
|
|
27
|
+
|
|
28
|
+
#### Main Thread (Sandbox)
|
|
29
|
+
|
|
30
|
+
The plugin's main code (`main` entry in manifest) runs on Figma's **main thread in a sandbox**. This sandbox is a minimal JavaScript environment that:
|
|
31
|
+
|
|
32
|
+
- Has access to the full Figma document tree (read and write)
|
|
33
|
+
- Provides standard ES6+ JavaScript (Promise, JSON, Uint8Array, etc.)
|
|
34
|
+
- Provides the `figma` global object with all Plugin API methods
|
|
35
|
+
- Does **NOT** expose browser APIs (no DOM, no `window`, no `document`)
|
|
36
|
+
- Does **NOT** have `fetch()`, `XMLHttpRequest`, or any network APIs
|
|
37
|
+
- Does **NOT** have `setTimeout`/`setInterval` (use `figma.timer` in FigJam only)
|
|
38
|
+
|
|
39
|
+
#### UI Thread (iframe)
|
|
40
|
+
|
|
41
|
+
The plugin's UI (`ui` entry in manifest) runs in a standard browser `<iframe>`. This iframe:
|
|
42
|
+
|
|
43
|
+
- Has full access to browser APIs (DOM, `fetch`, Canvas, WebAssembly, etc.)
|
|
44
|
+
- Can render HTML/CSS/JavaScript UIs
|
|
45
|
+
- Does **NOT** have direct access to the Figma document tree or Plugin API
|
|
46
|
+
- Must communicate with the main thread via message passing
|
|
47
|
+
|
|
48
|
+
#### Communication Flow
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
┌─────────────────────────┐ message passing ┌─────────────────────────┐
|
|
52
|
+
│ Main Thread │ ◄─────────────────────► │ UI iframe │
|
|
53
|
+
│ (Sandbox) │ │ (Browser) │
|
|
54
|
+
│ │ │ │
|
|
55
|
+
│ • figma.* API access │ figma.ui.postMessage │ • DOM / HTML / CSS │
|
|
56
|
+
│ • Document tree R/W │ ──────────────────────► │ • fetch() / XHR │
|
|
57
|
+
│ • Node creation/edit │ │ • Canvas / WebGL │
|
|
58
|
+
│ • Style/variable access │ parent.postMessage │ • WebAssembly │
|
|
59
|
+
│ • No DOM, no fetch │ ◄────────────────────── │ • No figma.* API │
|
|
60
|
+
└─────────────────────────┘ └─────────────────────────┘
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### Manifest: Standard Plugins
|
|
66
|
+
|
|
67
|
+
The `manifest.json` file configures a plugin. For **standard plugins** (design-mode plugins with optional UI), the manifest uses `editorType: ["figma"]` or `["figma", "figjam"]`.
|
|
68
|
+
|
|
69
|
+
> **Critical distinction:** Standard plugins use `editorType: ["figma"]`. Dev Mode codegen plugins use `editorType: ["dev"]` with `capabilities: ["codegen"]`. These are fundamentally different plugin types. See `figma-api-devmode.md` for codegen manifests.
|
|
70
|
+
|
|
71
|
+
#### Minimal Standard Plugin Manifest
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"name": "My Plugin",
|
|
76
|
+
"id": "1234567890",
|
|
77
|
+
"api": "1.0.0",
|
|
78
|
+
"main": "code.js",
|
|
79
|
+
"editorType": ["figma"],
|
|
80
|
+
"documentAccess": "dynamic-page"
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### Full Standard Plugin Manifest
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"name": "My Plugin",
|
|
89
|
+
"id": "1234567890",
|
|
90
|
+
"api": "1.0.0",
|
|
91
|
+
"main": "code.js",
|
|
92
|
+
"ui": "ui.html",
|
|
93
|
+
"editorType": ["figma", "figjam"],
|
|
94
|
+
"documentAccess": "dynamic-page",
|
|
95
|
+
"networkAccess": {
|
|
96
|
+
"allowedDomains": ["https://*.example.com"],
|
|
97
|
+
"reasoning": "Needed for syncing design tokens to server"
|
|
98
|
+
},
|
|
99
|
+
"permissions": ["currentuser"],
|
|
100
|
+
"menu": [
|
|
101
|
+
{
|
|
102
|
+
"name": "Export Components",
|
|
103
|
+
"command": "export"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"name": "Settings",
|
|
107
|
+
"command": "settings"
|
|
108
|
+
}
|
|
109
|
+
],
|
|
110
|
+
"relaunchButtons": [
|
|
111
|
+
{
|
|
112
|
+
"command": "relaunch",
|
|
113
|
+
"name": "Re-export",
|
|
114
|
+
"multipleSelection": false
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
"enableProposedApi": false
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### Manifest Field Reference
|
|
122
|
+
|
|
123
|
+
| Field | Type | Required | Description |
|
|
124
|
+
|-------|------|:--------:|-------------|
|
|
125
|
+
| `name` | string | Yes | Plugin display name |
|
|
126
|
+
| `id` | string | Yes | Unique plugin ID (assigned by Figma) |
|
|
127
|
+
| `api` | string | Yes | API version (e.g., `"1.0.0"`) |
|
|
128
|
+
| `main` | string | Yes | Path to main thread JavaScript entry |
|
|
129
|
+
| `ui` | string or object | No | Path to UI HTML file (string) or named UI files (object) |
|
|
130
|
+
| `editorType` | string[] | No | `["figma"]`, `["figjam"]`, `["figma", "figjam"]`, `["dev"]`, `["slides"]`, `["buzz"]` |
|
|
131
|
+
| `documentAccess` | string | Yes (new plugins) | Must be `"dynamic-page"` — pages load on demand |
|
|
132
|
+
| `networkAccess` | object | No | `allowedDomains` array, `reasoning` string, `devAllowedDomains` array |
|
|
133
|
+
| `permissions` | string[] | No | `"currentuser"`, `"activeusers"`, `"fileusers"`, `"payments"`, `"teamlibrary"` |
|
|
134
|
+
| `capabilities` | string[] | No | `"textreview"`, `"codegen"`, `"inspect"`, `"vscode"` (for Dev Mode plugins) |
|
|
135
|
+
| `menu` | object[] | No | Menu items with `name`, `command`, optional nested `menu` |
|
|
136
|
+
| `parameters` | object[] | No | Quick action parameters with `name`, `key`, `description`, `allowFreeform`, `optional` |
|
|
137
|
+
| `parameterOnly` | boolean | No | If true (default when parameters exist), plugin only runs via quick actions |
|
|
138
|
+
| `relaunchButtons` | object[] | No | Buttons attached to nodes for re-running plugin commands |
|
|
139
|
+
| `enableProposedApi` | boolean | No | Enable experimental API features |
|
|
140
|
+
| `build` | string | No | Build command for plugin bundling |
|
|
141
|
+
|
|
142
|
+
#### editorType Rules
|
|
143
|
+
|
|
144
|
+
- `["figma"]` — Runs in Figma Design editor only
|
|
145
|
+
- `["figjam"]` — Runs in FigJam only
|
|
146
|
+
- `["figma", "figjam"]` — Runs in both Figma Design and FigJam
|
|
147
|
+
- `["dev"]` — Runs in Dev Mode only (for codegen/inspect plugins)
|
|
148
|
+
- `["slides"]` — Runs in Figma Slides only
|
|
149
|
+
- `["buzz"]` — Runs in Figma Buzz only
|
|
150
|
+
- Cannot combine `"figjam"` with `"dev"`
|
|
151
|
+
|
|
152
|
+
#### networkAccess Patterns
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{ "allowedDomains": ["none"] }
|
|
156
|
+
{ "allowedDomains": ["*"] }
|
|
157
|
+
{ "allowedDomains": ["https://api.example.com", "https://*.cdn.example.com"] }
|
|
158
|
+
{ "allowedDomains": ["wss://realtime.example.com"] }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Network restrictions apply to requests made by the plugin (fetch, XHR), not to resources loaded by websites rendered within the iframe.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
### SceneNode Type Hierarchy
|
|
166
|
+
|
|
167
|
+
In Figma, every layer corresponds to a node. The Plugin API exposes these as TypeScript types.
|
|
168
|
+
|
|
169
|
+
#### Type Tree
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
BaseNode
|
|
173
|
+
├── DocumentNode
|
|
174
|
+
└── SceneNode
|
|
175
|
+
├── FrameNode (container, optional Auto Layout)
|
|
176
|
+
├── GroupNode (visual grouping, no layout properties)
|
|
177
|
+
├── SectionNode (organizational section on canvas)
|
|
178
|
+
├── ComponentNode (reusable component definition)
|
|
179
|
+
├── ComponentSetNode (variant container)
|
|
180
|
+
├── InstanceNode (instance of a component)
|
|
181
|
+
├── TextNode (text layer)
|
|
182
|
+
├── RectangleNode (rectangle shape)
|
|
183
|
+
├── EllipseNode (ellipse/circle shape)
|
|
184
|
+
├── VectorNode (arbitrary vector path)
|
|
185
|
+
├── LineNode (line segment)
|
|
186
|
+
├── StarNode (star shape)
|
|
187
|
+
├── PolygonNode (polygon shape)
|
|
188
|
+
├── BooleanOperationNode (union/subtract/intersect/exclude)
|
|
189
|
+
├── SliceNode (export region, not rendered)
|
|
190
|
+
├── StickyNode (FigJam only)
|
|
191
|
+
├── ShapeWithTextNode (FigJam only)
|
|
192
|
+
├── ConnectorNode (FigJam only)
|
|
193
|
+
├── CodeBlockNode (FigJam only)
|
|
194
|
+
├── TableNode (FigJam only)
|
|
195
|
+
├── EmbedNode (embedded content)
|
|
196
|
+
├── LinkUnfurlNode (link preview)
|
|
197
|
+
└── MediaNode (video/GIF)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
#### Key Mixins (Shared Interfaces)
|
|
201
|
+
|
|
202
|
+
Figma uses TypeScript mixins to share properties across node types:
|
|
203
|
+
|
|
204
|
+
| Mixin | Provides | Used By |
|
|
205
|
+
|-------|----------|---------|
|
|
206
|
+
| `ChildrenMixin` | `children`, `findAll()`, `findOne()`, `findChildren()`, `findAllWithCriteria()` | FrameNode, GroupNode, ComponentNode, InstanceNode, BooleanOperationNode, SectionNode |
|
|
207
|
+
| `LayoutMixin` | `layoutSizingHorizontal/Vertical`, `layoutGrow`, `layoutAlign`, `resize()`, `rescale()` | All SceneNodes |
|
|
208
|
+
| `AutoLayoutMixin` | `layoutMode`, `itemSpacing`, `paddingLeft/Right/Top/Bottom`, `primaryAxisAlignItems`, `counterAxisAlignItems`, `layoutWrap` | FrameNode, ComponentNode, ComponentSetNode, InstanceNode |
|
|
209
|
+
| `GeometryMixin` | `fills`, `strokes`, `strokeWeight`, `strokeAlign`, `fillGeometry`, `strokeGeometry` | FrameNode, RectangleNode, EllipseNode, VectorNode, TextNode, etc. |
|
|
210
|
+
| `CornerMixin` | `cornerRadius`, `cornerSmoothing` | FrameNode, RectangleNode, ComponentNode, InstanceNode |
|
|
211
|
+
| `RectangleCornerMixin` | `topLeftRadius`, `topRightRadius`, `bottomLeftRadius`, `bottomRightRadius` | FrameNode, RectangleNode, ComponentNode, InstanceNode |
|
|
212
|
+
| `BlendMixin` | `opacity`, `blendMode`, `effects`, `isMask` | All SceneNodes |
|
|
213
|
+
| `ExportMixin` | `exportAsync()`, `exportSettings` | All SceneNodes |
|
|
214
|
+
| `MinimalFillsMixin` | `fills` (read-only subset) | Some nodes |
|
|
215
|
+
| `MinimalStrokesMixin` | `strokes` (read-only subset) | Some nodes |
|
|
216
|
+
|
|
217
|
+
#### FrameNode vs GroupNode
|
|
218
|
+
|
|
219
|
+
This is a critical distinction for design-to-code:
|
|
220
|
+
|
|
221
|
+
| Property | FrameNode | GroupNode |
|
|
222
|
+
|----------|:---------:|:---------:|
|
|
223
|
+
| Auto Layout (`layoutMode`) | Yes | No |
|
|
224
|
+
| Padding properties | Yes | No |
|
|
225
|
+
| `itemSpacing` (gap) | Yes | No |
|
|
226
|
+
| `clipsContent` | Yes | No |
|
|
227
|
+
| `fills` (background) | Yes | No |
|
|
228
|
+
| `cornerRadius` | Yes | No |
|
|
229
|
+
| `effects` (shadows, blur) | Yes | No |
|
|
230
|
+
| `constraints` | Yes | No |
|
|
231
|
+
| Children positioning | Managed by layout | Absolute relative to group |
|
|
232
|
+
|
|
233
|
+
**Rule:** If a node has `layoutMode !== 'NONE'`, it is an Auto Layout frame and should map to a CSS flexbox container. Groups should map to a simple wrapper `<div>` with no layout styles.
|
|
234
|
+
|
|
235
|
+
#### ComponentNode vs InstanceNode
|
|
236
|
+
|
|
237
|
+
| Property | ComponentNode | InstanceNode |
|
|
238
|
+
|----------|:-------------:|:------------:|
|
|
239
|
+
| Is a definition | Yes | No (it is a usage) |
|
|
240
|
+
| `mainComponent` reference | N/A | Points to its ComponentNode |
|
|
241
|
+
| Overrides | N/A | Can override fills, text, visibility, etc. |
|
|
242
|
+
| Has component properties | Defines them | Consumes them |
|
|
243
|
+
| Can be detached | N/A | `detachInstance()` converts to FrameNode |
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
// Accessing the source component from an instance
|
|
247
|
+
if (node.type === 'INSTANCE') {
|
|
248
|
+
const component = await node.getMainComponentAsync();
|
|
249
|
+
if (component) {
|
|
250
|
+
console.log('Component name:', component.name);
|
|
251
|
+
console.log('Component key:', component.key);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
### Key Plugin API Methods
|
|
259
|
+
|
|
260
|
+
#### Document and Page Access
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
// Root document node
|
|
264
|
+
const doc: DocumentNode = figma.root;
|
|
265
|
+
|
|
266
|
+
// Current active page
|
|
267
|
+
const page: PageNode = figma.currentPage;
|
|
268
|
+
|
|
269
|
+
// Switch to a different page (async — loads page data)
|
|
270
|
+
await figma.setCurrentPageAsync(targetPage);
|
|
271
|
+
|
|
272
|
+
// Load all pages (needed if documentAccess is "dynamic-page")
|
|
273
|
+
await figma.loadAllPagesAsync();
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
#### Node Retrieval
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
// Get a specific node by ID (async — preferred)
|
|
280
|
+
const node = await figma.getNodeByIdAsync('1:2');
|
|
281
|
+
|
|
282
|
+
// Find nodes matching criteria in a subtree
|
|
283
|
+
const allText = page.findAll(node => node.type === 'TEXT');
|
|
284
|
+
const firstButton = page.findOne(node => node.name === 'Button');
|
|
285
|
+
const directChildren = frame.findChildren(node => node.visible);
|
|
286
|
+
|
|
287
|
+
// Find with specific criteria (more efficient)
|
|
288
|
+
const components = page.findAllWithCriteria({ types: ['COMPONENT'] });
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
#### Node Creation
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
// Create basic shapes
|
|
295
|
+
const frame = figma.createFrame();
|
|
296
|
+
const rect = figma.createRectangle();
|
|
297
|
+
const text = figma.createText();
|
|
298
|
+
const ellipse = figma.createEllipse();
|
|
299
|
+
const vector = figma.createVector();
|
|
300
|
+
const line = figma.createLine();
|
|
301
|
+
|
|
302
|
+
// Create components
|
|
303
|
+
const component = figma.createComponent();
|
|
304
|
+
const componentFromNode = figma.createComponentFromNode(existingNode);
|
|
305
|
+
|
|
306
|
+
// Create from SVG
|
|
307
|
+
const svgFrame = figma.createNodeFromSvg('<svg>...</svg>');
|
|
308
|
+
|
|
309
|
+
// Create a page
|
|
310
|
+
const newPage = figma.createPage();
|
|
311
|
+
|
|
312
|
+
// Create a section
|
|
313
|
+
const section = figma.createSection();
|
|
314
|
+
|
|
315
|
+
// Append to parent
|
|
316
|
+
frame.appendChild(rect);
|
|
317
|
+
page.appendChild(frame);
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
#### Font Loading
|
|
321
|
+
|
|
322
|
+
Font loading is **required** before reading or modifying text content:
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
// Load a specific font (MUST be called before modifying text)
|
|
326
|
+
await figma.loadFontAsync({ family: 'Inter', style: 'Regular' });
|
|
327
|
+
await figma.loadFontAsync({ family: 'Inter', style: 'Bold' });
|
|
328
|
+
|
|
329
|
+
// Now safe to modify text
|
|
330
|
+
textNode.characters = 'Hello World';
|
|
331
|
+
textNode.fontSize = 16;
|
|
332
|
+
|
|
333
|
+
// List available fonts
|
|
334
|
+
const fonts = await figma.listAvailableFontsAsync();
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
> **Important:** `figma.loadFontAsync()` must be called for each font family + style combination used in a text node before modifying that node's characters or style properties. Failure to load fonts before modification throws an error.
|
|
338
|
+
|
|
339
|
+
#### Exporting Nodes
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
// Export a node as PNG
|
|
343
|
+
const pngBytes: Uint8Array = await node.exportAsync({
|
|
344
|
+
format: 'PNG',
|
|
345
|
+
constraint: { type: 'SCALE', value: 2 }
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Export as SVG
|
|
349
|
+
const svgBytes: Uint8Array = await node.exportAsync({
|
|
350
|
+
format: 'SVG',
|
|
351
|
+
svgOutlineText: true,
|
|
352
|
+
svgIdAttribute: false
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Export as PDF
|
|
356
|
+
const pdfBytes: Uint8Array = await node.exportAsync({
|
|
357
|
+
format: 'PDF'
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Export as JPG
|
|
361
|
+
const jpgBytes: Uint8Array = await node.exportAsync({
|
|
362
|
+
format: 'JPG',
|
|
363
|
+
constraint: { type: 'SCALE', value: 1 }
|
|
364
|
+
});
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
#### Plugin Data Storage
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
// Store data on a node (private to this plugin)
|
|
371
|
+
node.setPluginData('metadata', JSON.stringify({ exported: true }));
|
|
372
|
+
|
|
373
|
+
// Read data from a node
|
|
374
|
+
const data = node.getPluginData('metadata');
|
|
375
|
+
|
|
376
|
+
// Store data shared across plugins
|
|
377
|
+
node.setSharedPluginData('namespace', 'key', 'value');
|
|
378
|
+
const shared = node.getSharedPluginData('namespace', 'key');
|
|
379
|
+
|
|
380
|
+
// Persistent client-side storage (survives plugin restarts)
|
|
381
|
+
await figma.clientStorage.setAsync('settings', { theme: 'dark' });
|
|
382
|
+
const settings = await figma.clientStorage.getAsync('settings');
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
#### Styles and Variables (Plugin API)
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
// Get local styles
|
|
389
|
+
const paintStyles = await figma.getLocalPaintStylesAsync();
|
|
390
|
+
const textStyles = await figma.getLocalTextStylesAsync();
|
|
391
|
+
const effectStyles = await figma.getLocalEffectStylesAsync();
|
|
392
|
+
|
|
393
|
+
// Get a style by ID
|
|
394
|
+
const style = await figma.getStyleByIdAsync('S:abc123');
|
|
395
|
+
|
|
396
|
+
// Import a published style by key
|
|
397
|
+
const importedStyle = await figma.importStyleByKeyAsync('component-key');
|
|
398
|
+
|
|
399
|
+
// Import a published component by key
|
|
400
|
+
const component = await figma.importComponentByKeyAsync('component-key');
|
|
401
|
+
|
|
402
|
+
// Access local variables (Plugin API — works on all plans)
|
|
403
|
+
const variables = await figma.variables.getLocalVariablesAsync();
|
|
404
|
+
const collections = await figma.variables.getLocalVariableCollectionsAsync();
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
> **Note:** The Plugin API `figma.variables` gives access to local variables on ALL Figma plans. The REST API Variables endpoints require Enterprise. This makes the Plugin API the preferred fallback for variable access on non-Enterprise plans.
|
|
408
|
+
|
|
409
|
+
#### Events
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
// Run event — fired when plugin starts
|
|
413
|
+
figma.on('run', (event: RunEvent) => {
|
|
414
|
+
console.log('Command:', event.command);
|
|
415
|
+
console.log('Parameters:', event.parameters);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// Selection change
|
|
419
|
+
figma.on('selectionchange', () => {
|
|
420
|
+
const selection = figma.currentPage.selection;
|
|
421
|
+
console.log('Selected:', selection.length, 'nodes');
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Document change
|
|
425
|
+
figma.on('documentchange', (event: DocumentChangeEvent) => {
|
|
426
|
+
for (const change of event.documentChanges) {
|
|
427
|
+
console.log('Changed:', change.type, change.id);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Close event — cleanup before plugin closes
|
|
432
|
+
figma.on('close', () => {
|
|
433
|
+
// Perform cleanup
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// Drop event — handle drag-and-drop onto canvas
|
|
437
|
+
figma.on('drop', (event: DropEvent) => {
|
|
438
|
+
const { items, node, dropMetadata } = event;
|
|
439
|
+
// Handle dropped items
|
|
440
|
+
return false; // return true to prevent default behavior
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
#### UI Management
|
|
445
|
+
|
|
446
|
+
```ts
|
|
447
|
+
// Show the UI iframe
|
|
448
|
+
figma.showUI(__html__, {
|
|
449
|
+
width: 400,
|
|
450
|
+
height: 600,
|
|
451
|
+
title: 'My Plugin',
|
|
452
|
+
visible: true,
|
|
453
|
+
themeColors: true // Use Figma's theme colors
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
// Show named UI (when ui is an object in manifest)
|
|
457
|
+
figma.showUI(__uiFiles__['settings'], { width: 300, height: 200 });
|
|
458
|
+
|
|
459
|
+
// Resize after creation
|
|
460
|
+
figma.ui.resize(500, 700);
|
|
461
|
+
|
|
462
|
+
// Reposition
|
|
463
|
+
figma.ui.reposition(100, 100);
|
|
464
|
+
|
|
465
|
+
// Hide without destroying
|
|
466
|
+
figma.ui.hide();
|
|
467
|
+
figma.ui.show();
|
|
468
|
+
|
|
469
|
+
// Close and destroy
|
|
470
|
+
figma.ui.close();
|
|
471
|
+
|
|
472
|
+
// Close the entire plugin
|
|
473
|
+
figma.closePlugin('Done!'); // Optional message shown to user
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
#### Utilities
|
|
477
|
+
|
|
478
|
+
```ts
|
|
479
|
+
// Show a notification toast
|
|
480
|
+
figma.notify('Export complete!', { timeout: 3000 });
|
|
481
|
+
figma.notify('Error occurred', { error: true });
|
|
482
|
+
|
|
483
|
+
// Open external URL (requires user confirmation)
|
|
484
|
+
figma.openExternal('https://example.com');
|
|
485
|
+
|
|
486
|
+
// Base64 encode/decode
|
|
487
|
+
const encoded = figma.base64Encode(uint8Array);
|
|
488
|
+
const decoded = figma.base64Decode(encodedString);
|
|
489
|
+
|
|
490
|
+
// Create an image from bytes
|
|
491
|
+
const image = figma.createImage(pngBytes);
|
|
492
|
+
|
|
493
|
+
// Create an image from URL (fetched by Figma)
|
|
494
|
+
const image = await figma.createImageAsync('https://example.com/image.png');
|
|
495
|
+
|
|
496
|
+
// Save version history
|
|
497
|
+
await figma.saveVersionHistoryAsync('Auto-export v2', 'Exported all components');
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
### IPC Messaging Patterns
|
|
503
|
+
|
|
504
|
+
Communication between the main thread and UI iframe uses structured message passing.
|
|
505
|
+
|
|
506
|
+
#### Basic Pattern: Main Thread to UI
|
|
507
|
+
|
|
508
|
+
```ts
|
|
509
|
+
// Main thread (code.ts)
|
|
510
|
+
figma.showUI(__html__);
|
|
511
|
+
|
|
512
|
+
// Send data to UI
|
|
513
|
+
figma.ui.postMessage({
|
|
514
|
+
type: 'NODE_DATA',
|
|
515
|
+
payload: {
|
|
516
|
+
name: node.name,
|
|
517
|
+
width: node.width,
|
|
518
|
+
height: node.height
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
```html
|
|
524
|
+
<!-- UI (ui.html) -->
|
|
525
|
+
<script>
|
|
526
|
+
window.onmessage = (event) => {
|
|
527
|
+
const msg = event.data.pluginMessage;
|
|
528
|
+
if (msg.type === 'NODE_DATA') {
|
|
529
|
+
document.getElementById('name').textContent = msg.payload.name;
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
</script>
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
#### Basic Pattern: UI to Main Thread
|
|
536
|
+
|
|
537
|
+
```html
|
|
538
|
+
<!-- UI (ui.html) -->
|
|
539
|
+
<script>
|
|
540
|
+
function exportSelection() {
|
|
541
|
+
parent.postMessage({
|
|
542
|
+
pluginMessage: {
|
|
543
|
+
type: 'EXPORT',
|
|
544
|
+
format: 'svg',
|
|
545
|
+
scale: 2
|
|
546
|
+
}
|
|
547
|
+
}, '*');
|
|
548
|
+
}
|
|
549
|
+
</script>
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
// Main thread (code.ts)
|
|
554
|
+
figma.ui.onmessage = (msg) => {
|
|
555
|
+
if (msg.type === 'EXPORT') {
|
|
556
|
+
// Handle export request
|
|
557
|
+
const bytes = await node.exportAsync({ format: msg.format });
|
|
558
|
+
figma.ui.postMessage({ type: 'EXPORT_RESULT', data: bytes });
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
// Alternative: multiple handlers with .on()
|
|
563
|
+
figma.ui.on('message', (msg) => {
|
|
564
|
+
if (msg.type === 'EXPORT') { /* ... */ }
|
|
565
|
+
});
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
#### Pattern: Request/Response with Correlation IDs
|
|
569
|
+
|
|
570
|
+
For complex plugins with concurrent operations, use correlation IDs to match responses:
|
|
571
|
+
|
|
572
|
+
```ts
|
|
573
|
+
// Main thread (code.ts)
|
|
574
|
+
figma.ui.on('message', async (msg) => {
|
|
575
|
+
if (msg.type === 'FETCH_REQUEST') {
|
|
576
|
+
// Process the request
|
|
577
|
+
const nodes = figma.currentPage.findAll(n => n.type === msg.nodeType);
|
|
578
|
+
const result = nodes.map(n => ({ id: n.id, name: n.name }));
|
|
579
|
+
|
|
580
|
+
figma.ui.postMessage({
|
|
581
|
+
type: 'FETCH_RESPONSE',
|
|
582
|
+
requestId: msg.requestId, // Echo the correlation ID
|
|
583
|
+
payload: result
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
```html
|
|
590
|
+
<!-- UI (ui.html) -->
|
|
591
|
+
<script>
|
|
592
|
+
let nextRequestId = 1;
|
|
593
|
+
const pendingRequests = new Map();
|
|
594
|
+
|
|
595
|
+
function fetchNodes(nodeType) {
|
|
596
|
+
return new Promise((resolve) => {
|
|
597
|
+
const requestId = nextRequestId++;
|
|
598
|
+
pendingRequests.set(requestId, resolve);
|
|
599
|
+
parent.postMessage({
|
|
600
|
+
pluginMessage: {
|
|
601
|
+
type: 'FETCH_REQUEST',
|
|
602
|
+
requestId,
|
|
603
|
+
nodeType
|
|
604
|
+
}
|
|
605
|
+
}, '*');
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
window.onmessage = (event) => {
|
|
610
|
+
const msg = event.data.pluginMessage;
|
|
611
|
+
if (msg.type === 'FETCH_RESPONSE' && pendingRequests.has(msg.requestId)) {
|
|
612
|
+
pendingRequests.get(msg.requestId)(msg.payload);
|
|
613
|
+
pendingRequests.delete(msg.requestId);
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
</script>
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
#### Pattern: Network Proxy (fetch via UI)
|
|
620
|
+
|
|
621
|
+
Since the main thread cannot make network requests, proxy them through the UI:
|
|
622
|
+
|
|
623
|
+
```ts
|
|
624
|
+
// Main thread (code.ts)
|
|
625
|
+
figma.ui.postMessage({
|
|
626
|
+
type: 'API_REQUEST',
|
|
627
|
+
url: 'https://api.example.com/tokens',
|
|
628
|
+
method: 'POST',
|
|
629
|
+
headers: { 'Content-Type': 'application/json' },
|
|
630
|
+
body: JSON.stringify(tokenData)
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
figma.ui.on('message', (msg) => {
|
|
634
|
+
if (msg.type === 'API_RESPONSE') {
|
|
635
|
+
if (msg.ok) {
|
|
636
|
+
figma.notify('Tokens synced!');
|
|
637
|
+
} else {
|
|
638
|
+
figma.notify('Sync failed: ' + msg.error, { error: true });
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
```html
|
|
645
|
+
<!-- UI (ui.html) -->
|
|
646
|
+
<script>
|
|
647
|
+
window.onmessage = async (event) => {
|
|
648
|
+
const msg = event.data.pluginMessage;
|
|
649
|
+
if (msg.type === 'API_REQUEST') {
|
|
650
|
+
try {
|
|
651
|
+
const res = await fetch(msg.url, {
|
|
652
|
+
method: msg.method,
|
|
653
|
+
headers: msg.headers,
|
|
654
|
+
body: msg.body
|
|
655
|
+
});
|
|
656
|
+
const data = await res.json();
|
|
657
|
+
parent.postMessage({
|
|
658
|
+
pluginMessage: {
|
|
659
|
+
type: 'API_RESPONSE',
|
|
660
|
+
ok: res.ok,
|
|
661
|
+
status: res.status,
|
|
662
|
+
data
|
|
663
|
+
}
|
|
664
|
+
}, '*');
|
|
665
|
+
} catch (err) {
|
|
666
|
+
parent.postMessage({
|
|
667
|
+
pluginMessage: {
|
|
668
|
+
type: 'API_RESPONSE',
|
|
669
|
+
ok: false,
|
|
670
|
+
error: err.message
|
|
671
|
+
}
|
|
672
|
+
}, '*');
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
</script>
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
681
|
+
### Common Plugin Patterns
|
|
682
|
+
|
|
683
|
+
#### Walking the Node Tree
|
|
684
|
+
|
|
685
|
+
```ts
|
|
686
|
+
function walkTree(node: BaseNode, callback: (node: BaseNode) => void): void {
|
|
687
|
+
callback(node);
|
|
688
|
+
if ('children' in node) {
|
|
689
|
+
for (const child of node.children) {
|
|
690
|
+
walkTree(child, callback);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Usage: collect all text content
|
|
696
|
+
const textNodes: TextNode[] = [];
|
|
697
|
+
walkTree(figma.currentPage, (node) => {
|
|
698
|
+
if (node.type === 'TEXT') {
|
|
699
|
+
textNodes.push(node as TextNode);
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
#### Reading Auto Layout Properties
|
|
705
|
+
|
|
706
|
+
```ts
|
|
707
|
+
function getLayoutInfo(node: FrameNode | ComponentNode | InstanceNode) {
|
|
708
|
+
if (node.layoutMode === 'NONE') {
|
|
709
|
+
return { isAutoLayout: false };
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return {
|
|
713
|
+
isAutoLayout: true,
|
|
714
|
+
direction: node.layoutMode, // 'HORIZONTAL' | 'VERTICAL'
|
|
715
|
+
gap: node.itemSpacing, // number (maps to CSS gap)
|
|
716
|
+
wrap: node.layoutWrap, // 'NO_WRAP' | 'WRAP'
|
|
717
|
+
padding: {
|
|
718
|
+
top: node.paddingTop,
|
|
719
|
+
right: node.paddingRight,
|
|
720
|
+
bottom: node.paddingBottom,
|
|
721
|
+
left: node.paddingLeft,
|
|
722
|
+
},
|
|
723
|
+
primaryAlign: node.primaryAxisAlignItems, // 'MIN' | 'CENTER' | 'MAX' | 'SPACE_BETWEEN'
|
|
724
|
+
crossAlign: node.counterAxisAlignItems, // 'MIN' | 'CENTER' | 'MAX' | 'BASELINE'
|
|
725
|
+
crossContentAlign: node.counterAxisAlignContent, // 'AUTO' | 'SPACE_BETWEEN'
|
|
726
|
+
sizingH: node.layoutSizingHorizontal, // 'FIXED' | 'HUG' | 'FILL'
|
|
727
|
+
sizingV: node.layoutSizingVertical, // 'FIXED' | 'HUG' | 'FILL'
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
#### Reading Visual Properties
|
|
733
|
+
|
|
734
|
+
```ts
|
|
735
|
+
function getVisualInfo(node: SceneNode) {
|
|
736
|
+
const info: Record<string, any> = {
|
|
737
|
+
opacity: 'opacity' in node ? node.opacity : 1,
|
|
738
|
+
blendMode: 'blendMode' in node ? node.blendMode : 'NORMAL',
|
|
739
|
+
visible: node.visible,
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
// Fills
|
|
743
|
+
if ('fills' in node && node.fills !== figma.mixed) {
|
|
744
|
+
info.fills = (node.fills as readonly Paint[]).filter(f => f.visible !== false);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Strokes
|
|
748
|
+
if ('strokes' in node) {
|
|
749
|
+
info.strokes = node.strokes;
|
|
750
|
+
info.strokeWeight = 'strokeWeight' in node ? node.strokeWeight : 0;
|
|
751
|
+
info.strokeAlign = 'strokeAlign' in node ? node.strokeAlign : 'CENTER';
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Corner radius
|
|
755
|
+
if ('cornerRadius' in node) {
|
|
756
|
+
if (node.cornerRadius !== figma.mixed) {
|
|
757
|
+
info.cornerRadius = node.cornerRadius;
|
|
758
|
+
} else if ('topLeftRadius' in node) {
|
|
759
|
+
info.cornerRadius = [
|
|
760
|
+
node.topLeftRadius,
|
|
761
|
+
node.topRightRadius,
|
|
762
|
+
node.bottomRightRadius,
|
|
763
|
+
node.bottomLeftRadius
|
|
764
|
+
];
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Effects (shadows, blur)
|
|
769
|
+
if ('effects' in node) {
|
|
770
|
+
info.effects = node.effects.filter(e => e.visible !== false);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
return info;
|
|
774
|
+
}
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
#### Processing Component Instances
|
|
778
|
+
|
|
779
|
+
```ts
|
|
780
|
+
async function processInstances(page: PageNode) {
|
|
781
|
+
const instances = page.findAllWithCriteria({ types: ['INSTANCE'] }) as InstanceNode[];
|
|
782
|
+
|
|
783
|
+
for (const instance of instances) {
|
|
784
|
+
const mainComponent = await instance.getMainComponentAsync();
|
|
785
|
+
|
|
786
|
+
if (!mainComponent) {
|
|
787
|
+
console.log(`${instance.name}: detached or missing component`);
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// Check if from external library
|
|
792
|
+
const isRemote = mainComponent.remote;
|
|
793
|
+
const componentKey = mainComponent.key;
|
|
794
|
+
const componentName = mainComponent.name;
|
|
795
|
+
|
|
796
|
+
// Access overridden properties
|
|
797
|
+
const overrides = instance.overrides;
|
|
798
|
+
|
|
799
|
+
console.log(`Instance "${instance.name}" → Component "${componentName}" (remote: ${isRemote})`);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
---
|
|
805
|
+
|
|
806
|
+
### Plugin Constraints and Limitations
|
|
807
|
+
|
|
808
|
+
#### Execution Constraints
|
|
809
|
+
|
|
810
|
+
| Constraint | Details |
|
|
811
|
+
|-----------|---------|
|
|
812
|
+
| **Single plugin at a time** | Users can only run one plugin and one widget simultaneously |
|
|
813
|
+
| **No background execution** | Plugins cannot run in the background; they must be actively invoked |
|
|
814
|
+
| **Plugin lifecycle** | Must call `figma.closePlugin()` to terminate (unless using `figma.on('close', ...)`) |
|
|
815
|
+
| **Dynamic page loading** | Pages load on demand (`documentAccess: "dynamic-page"`). Use `figma.loadAllPagesAsync()` if you need all pages |
|
|
816
|
+
| **Asynchronous operations** | Font loading, page switching, image creation, and many node lookups are async |
|
|
817
|
+
|
|
818
|
+
#### Main Thread Sandbox Restrictions
|
|
819
|
+
|
|
820
|
+
| Restriction | Details |
|
|
821
|
+
|------------|---------|
|
|
822
|
+
| **No DOM access** | `document`, `window`, `HTMLElement`, etc. are not available |
|
|
823
|
+
| **No network access** | `fetch()`, `XMLHttpRequest`, WebSocket are not available — proxy through UI iframe |
|
|
824
|
+
| **No timers** | `setTimeout`, `setInterval` are not available in the main sandbox (except FigJam `figma.timer`) |
|
|
825
|
+
| **No external fonts** | Can only load fonts available in the Figma editor via `figma.loadFontAsync()` |
|
|
826
|
+
| **No file metadata** | Cannot access team info, permissions, comments, or version history (except `figma.saveVersionHistoryAsync()`) |
|
|
827
|
+
|
|
828
|
+
#### Data and Access Limitations
|
|
829
|
+
|
|
830
|
+
| Limitation | Details |
|
|
831
|
+
|-----------|---------|
|
|
832
|
+
| **Library access** | Cannot access library styles/components unless imported into the current file |
|
|
833
|
+
| **Font requirement** | Must call `figma.loadFontAsync()` before modifying any text node's characters or style |
|
|
834
|
+
| **Image data** | Can create images from bytes or URL, but cannot read pixel data from existing image fills directly |
|
|
835
|
+
| **Plugin data scope** | `setPluginData()` is private to the plugin ID; use `setSharedPluginData()` for cross-plugin data |
|
|
836
|
+
| **Client storage** | `figma.clientStorage` is local to the user's machine and plugin ID |
|
|
837
|
+
|
|
838
|
+
#### Performance Considerations
|
|
839
|
+
|
|
840
|
+
- Avoid calling `figma.root.findAll()` on large files — use page-level searches or `findAllWithCriteria()` for type-filtered searches
|
|
841
|
+
- Batch node modifications before yielding control to reduce re-renders
|
|
842
|
+
- Use `figma.skipInvisibleInstanceChildren = true` to skip hidden instance children during traversal
|
|
843
|
+
- Prefer async API variants (`getNodeByIdAsync`, `getLocalPaintStylesAsync`, etc.) — sync versions are deprecated
|
|
844
|
+
|
|
845
|
+
---
|
|
846
|
+
|
|
847
|
+
### @create-figma-plugin Tooling
|
|
848
|
+
|
|
849
|
+
The `@create-figma-plugin` package is a popular open-source toolkit for building Figma plugins with modern tooling:
|
|
850
|
+
|
|
851
|
+
```bash
|
|
852
|
+
npx create-figma-plugin
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
Key features:
|
|
856
|
+
- TypeScript support out of the box
|
|
857
|
+
- Preact-based UI components matching Figma's design
|
|
858
|
+
- Build system with esbuild
|
|
859
|
+
- Module-based plugin structure (separates handler and UI)
|
|
860
|
+
- Automatic manifest generation from package.json
|
|
861
|
+
|
|
862
|
+
**Handler pattern:**
|
|
863
|
+
|
|
864
|
+
```ts
|
|
865
|
+
// src/main.ts
|
|
866
|
+
import { on, emit, showUI } from '@create-figma-plugin/utilities';
|
|
867
|
+
|
|
868
|
+
export default function () {
|
|
869
|
+
on('EXPORT_REQUEST', async (format: string) => {
|
|
870
|
+
const selection = figma.currentPage.selection;
|
|
871
|
+
// Process selection...
|
|
872
|
+
emit('EXPORT_RESULT', { success: true, count: selection.length });
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
showUI({ width: 400, height: 300 });
|
|
876
|
+
}
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
```tsx
|
|
880
|
+
// src/ui.tsx
|
|
881
|
+
import { render, useWindowResize } from '@create-figma-plugin/ui';
|
|
882
|
+
import { emit, on } from '@create-figma-plugin/utilities';
|
|
883
|
+
import { h } from 'preact';
|
|
884
|
+
import { useState } from 'preact/hooks';
|
|
885
|
+
|
|
886
|
+
function Plugin() {
|
|
887
|
+
const [result, setResult] = useState(null);
|
|
888
|
+
|
|
889
|
+
on('EXPORT_RESULT', (data) => setResult(data));
|
|
890
|
+
|
|
891
|
+
function handleExport() {
|
|
892
|
+
emit('EXPORT_REQUEST', 'svg');
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
return (
|
|
896
|
+
<div>
|
|
897
|
+
<button onclick={handleExport}>Export</button>
|
|
898
|
+
{result && <p>Exported {result.count} nodes</p>}
|
|
899
|
+
</div>
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
export default render(Plugin);
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
The `emit`/`on` helpers from `@create-figma-plugin/utilities` abstract the raw `postMessage`/`onmessage` IPC pattern into a typed event system.
|
|
907
|
+
|
|
908
|
+
---
|
|
909
|
+
|
|
910
|
+
## Cross-References
|
|
911
|
+
|
|
912
|
+
- **`figma-api-rest.md`** — REST API for file/node fetching from outside Figma (authentication, endpoints, rate limits)
|
|
913
|
+
- **`figma-api-devmode.md`** — Dev Mode codegen plugins (different manifest, different lifecycle, `figma.codegen` API)
|
|
914
|
+
- **`figma-api-variables.md`** — Variables REST API for design tokens (Enterprise only; Plugin API `figma.variables` works on all plans)
|
|
915
|
+
- **`figma-api-webhooks.md`** — Webhooks v2 for reacting to file changes externally
|
|
916
|
+
- **`design-to-code-layout.md`** — Auto Layout to Flexbox mapping (consumes layout properties from Plugin API)
|
|
917
|
+
- **`design-to-code-visual.md`** — Visual property extraction patterns (fills, strokes, effects)
|
|
918
|
+
- **`design-to-code-assets.md`** — Asset export patterns using `node.exportAsync()`
|
|
919
|
+
- **`plugin-architecture.md`** — Production plugin architecture patterns (project setup, IPC design, data flow pipeline)
|
|
920
|
+
- **`plugin-best-practices.md`** — Best practices for Figma plugin development
|