| name | figma-plugins |
| description | Build Figma plugins with full Plugin API access. Use when creating plugins for Figma, FigJam, Slides, or Dev Mode that manipulate designs, generate content, export assets, manage styles/variables, work with components, integrate external services, or monetize with payments. Covers manifest configuration, sandbox architecture, UI development with React, node manipulation, auto layout, text/font handling, image processing, variables (design tokens), annotations, codegen, payments, data storage, and publishing. Also trigger for Figma Slides plugin development, Dev Mode codegen plugins, or any work involving the Figma Plugin API. |
Figma Plugin Development
Architecture Overview
Figma plugins consist of three parts:
- manifest.json — Configuration, permissions, and capabilities
- Main code (code.ts) — Runs in QuickJS sandbox with
figma API access
- UI (optional) — HTML/CSS/JS iframe with browser APIs
┌─────────────────────────────────────────────────────────┐
│ MAIN THREAD (Sandbox) │ UI IFRAME (Browser) │
│ ✓ figma global API │ ✓ DOM, fetch, Canvas │
│ ✓ Document manipulation │ ✓ React/Vue/Svelte │
│ ✓ Native fetch API │ ✓ WebSockets │
│ ✗ No DOM access │ ✗ No figma API │
│ └────── postMessage ──────┘ │
└─────────────────────────────────────────────────────────┘
Important: The sandbox now has a native Fetch API — network requests no longer require the UI iframe.
Quick Start
Minimal manifest.json
{
"name": "My Plugin",
"id": "000000000000000000",
"api": "1.0.0",
"main": "code.js",
"editorType": ["figma"],
"documentAccess": "dynamic-page"
}
Minimal Plugin (No UI)
const rect = figma.createRectangle();
rect.fills = [figma.util.solidPaint('#FF5500')];
figma.currentPage.appendChild(rect);
figma.closePlugin('Created rectangle');
Plugin with UI
figma.showUI(__html__, { width: 300, height: 200, themeColors: true });
figma.ui.onmessage = async (msg) => {
if (msg.type === 'create-rect') {
const rect = figma.createRectangle();
rect.resize(msg.width, msg.height);
figma.currentPage.appendChild(rect);
}
figma.closePlugin();
};
<button id="create">Create</button>
<script>
document.getElementById('create').onclick = () => {
parent.postMessage({ pluginMessage: { type: 'create-rect', width: 100, height: 100 } }, '*');
};
</script>
Manifest Configuration
Required Fields
| Field | Description |
|---|
name | Plugin name in Figma menu |
id | Unique ID (assigned by Figma on publish) |
api | API version, always "1.0.0" |
main | Path to compiled JavaScript entry |
editorType | Array: "figma", "figjam", "dev", "slides", "buzz" |
documentAccess | Always "dynamic-page" — mandatory for all new plugins |
Editor Types
Five editor types with distinct API surfaces:
"figma" — Full design editor (default)
"figjam" — Whiteboard (stickies, connectors, tables, stamps, timer)
"dev" — Dev Mode (read-only, codegen, inspect, dev resources)
"slides" — Figma Slides (SlideNode, SlideRowNode, transitions)
"buzz" — Figma Buzz (marketing assets, smart resize)
Invalid combinations: ["figjam", "dev"] and ["slides", "dev"].
Permissions
{ "permissions": ["currentuser", "activeusers", "fileusers", "teamlibrary", "payments"] }
Capabilities
{ "capabilities": ["codegen", "inspect", "textreview", "vscode"] }
codegen — Code generation in Dev Mode (requires codegenLanguages)
inspect — Custom inspect panel in Dev Mode
textreview — Text review/linting (fires textreview event)
vscode — VS Code integration
Network Access
{
"networkAccess": {
"allowedDomains": ["api.example.com", "*.example.com"],
"reasoning": "Required for fetching data from our API",
"devAllowedDomains": ["http://localhost:3000"]
}
}
Supports wildcards, WebSocket schemes (ws://, wss://), ["none"], or ["*"] (unrestricted, requires reasoning). Plugin iframes have a null origin — CORS with Access-Control-Allow-Origin: * is required on target servers.
Menu Commands and Mode
{
"menu": [
{ "name": "Create Shape", "command": "create" },
{ "separator": true },
{ "name": "Settings", "command": "settings" }
]
}
figma.on('run', ({ command }) => {
if (command === 'settings') figma.showUI(__uiFiles__['settings']);
});
figma.mode
Project Setup
TypeScript Configuration
npm init -y
npm install --save-dev typescript @figma/plugin-typings esbuild
tsconfig.json:
{
"compilerOptions": {
"target": "ES2020", "lib": ["ES2020"], "strict": true,
"typeRoots": ["./node_modules/@types", "./node_modules/@figma"],
"outDir": "./dist"
},
"include": ["src/**/*.ts"]
}
Current @figma/plugin-typings: 1.121.0. Use eslint-plugin-figma-plugins to catch deprecated sync API calls.
Build with esbuild
import * as esbuild from 'esbuild';
await esbuild.build({
entryPoints: ['src/code.ts'], bundle: true, outfile: 'dist/code.js',
target: 'es2020', minify: process.argv.includes('--minify'),
});
Essential Patterns
Selection Handling
const selection = figma.currentPage.selection;
if (selection.length === 0) {
figma.notify('Select at least one layer', { error: true });
figma.closePlugin();
return;
}
for (const node of selection) {
if (node.type === 'TEXT') { }
}
Node Creation
const rect = figma.createRectangle();
const frame = figma.createFrame();
const text = figma.createText();
const component = figma.createComponent();
const section = figma.createSection();
const slide = figma.createSlide();
const slideRow = figma.createSlideRow();
node.x = 100; node.y = 200;
node.resize(300, 200);
frame.appendChild(rect);
Colors and Fills
RGB values are 0-1, not 0-255.
const solidPaint = figma.util.solidPaint('#FF5500');
rect.fills = [{ type: 'SOLID', color: { r: 1, g: 0.5, b: 0 }, opacity: 0.8 }];
const fills = JSON.parse(JSON.stringify(rect.fills));
fills[0].color.r = 0.5;
rect.fills = fills;
Text (MUST Load Fonts)
const text = figma.createText();
await figma.loadFontAsync({ family: 'Inter', style: 'Regular' });
text.characters = 'Hello World';
text.fontSize = 24;
text.fontName = { family: 'Inter', style: 'Bold' };
text.setRangeFontSize(0, 5, 32);
text.setRangeFills(0, 5, [figma.util.solidPaint('#FF0000')]);
text.setRangeHyperlink(0, 5, { type: 'URL', value: 'https://example.com' });
if (text.fontName === figma.mixed) {
const fonts = text.getRangeAllFontNames(0, text.characters.length);
await Promise.all(fonts.map(f => figma.loadFontAsync(f)));
}
Auto Layout
const frame = figma.createFrame();
frame.layoutMode = 'HORIZONTAL';
frame.primaryAxisSizingMode = 'AUTO';
frame.counterAxisSizingMode = 'AUTO';
frame.itemSpacing = 16;
frame.paddingTop = frame.paddingBottom = 20;
frame.paddingLeft = frame.paddingRight = 16;
frame.primaryAxisAlignItems = 'CENTER';
frame.counterAxisAlignItems = 'CENTER';
frame.layoutWrap = 'WRAP';
frame.counterAxisSpacing = 12;
child.layoutSizingHorizontal = 'FILL';
Variables (Design Tokens)
Variables are the API for design tokens. See references/variables-api.md for the full reference.
const collection = figma.variables.createVariableCollection('Colors');
const primaryColor = figma.variables.createVariable('primary', collection, 'COLOR');
primaryColor.setValueForMode(collection.defaultModeId, { r: 0.2, g: 0.4, b: 1 });
rect.setBoundVariable('fills', primaryColor);
rect.setBoundVariable('cornerRadius', radiusVar);
rect.setBoundVariable('itemSpacing', spacingVar);
const allColors = await figma.variables.getLocalVariablesAsync('COLOR');
const libVar = await figma.variables.importVariableByKeyAsync(key);
Annotations
node.annotations = [{
label: 'Ready for development',
properties: [{ type: 'fills' }, { type: 'width' }, { type: 'fontSize' }]
}];
const categories = await figma.annotations.getAnnotationCategoriesAsync();
Components and Instances
const component = figma.createComponent();
component.addComponentProperty("Label", "TEXT", "Button");
component.addComponentProperty("Disabled", "BOOLEAN", false);
component.addComponentProperty("Icon", "INSTANCE_SWAP", iconId, {
preferredValues: [{ type: 'COMPONENT', key: starIcon.key }]
});
const instance = component.createInstance();
instance.setProperties({ 'Label#0:1': 'Submit' });
const main = await instance.getMainComponentAsync();
Data Storage
node.setPluginData('key', JSON.stringify({ value: 1 }));
const data = JSON.parse(node.getPluginData('key') || '{}');
await figma.clientStorage.setAsync('prefs', { theme: 'dark' });
const prefs = await figma.clientStorage.getAsync('prefs');
Images
const image = await figma.createImageAsync('https://example.com/image.png');
rect.fills = [{ type: 'IMAGE', imageHash: image.hash, scaleMode: 'FILL' }];
const bytes = await node.exportAsync({ format: 'PNG', constraint: { type: 'SCALE', value: 2 } });
Payments (Monetization)
Requires "payments" in manifest permissions.
const { status } = figma.payments;
if (status.type === 'UNPAID') {
await figma.payments.initiateCheckoutAsync({ interstitial: 'TRIAL_ENDED' });
} else if (status.type === 'PAID') { }
const secondsSinceFirstRun = figma.payments.getUserFirstRanSecondsAgo();
UI Communication
figma.ui.postMessage({ type: 'data', items: [1, 2, 3] });
parent.postMessage({ pluginMessage: { type: 'action', data: 123 } }, '*');
Theme CSS Variables
With themeColors: true, detect dark mode via .figma-dark on <body>:
body { background: var(--figma-color-bg); color: var(--figma-color-text); }
.button { background: var(--figma-color-bg-brand); color: var(--figma-color-text-onbrand); }
Plugin UI Libraries
@create-figma-plugin/ui (v4+) — Best option. Preact components matching Figma UI3.
figma-plugin-ds — Vanilla CSS/JS, older UI2 style.
- Figma's UI3 Kit is a design file only (no code components).
Events
figma.on('run', ({ command, parameters }) => { });
figma.on('selectionchange', () => { });
figma.on('currentpagechange', () => { });
figma.on('documentchange', (event) => { });
figma.on('drop', (event) => { return false; });
figma.on('stylechange', (event) => { });
figma.on('close', () => { });
figma.currentPage.on('nodechange', ({ nodeChanges }) => { });
Performance
figma.skipInvisibleInstanceChildren = true;
const texts = figma.currentPage.findAllWithCriteria({ types: ['TEXT'] });
const tagged = figma.currentPage.findAllWithCriteria({ pluginData: { keys: ['myKey'] } });
await page.loadAsync();
Critical Rules
- Always call
figma.closePlugin() — Plugin runs forever otherwise
- Use
documentAccess: "dynamic-page" — Mandatory for all new plugins
- Use async API methods — Sync versions are deprecated. Use
Async variants.
- Load fonts before text changes — Throws without loaded font
- Clone arrays before modifying — fills, effects, strokes are read-only
- No async in
close event — Must be synchronous
- Declare network domains — CSP blocks undeclared domains
- RGB values are 0-1 — Not 0-255
- UI messages need
pluginMessage wrapper — { pluginMessage: data }
- Dev Mode plugins are read-only — Cannot create or modify nodes
- Codegen
generate has 15s timeout — No showUI() inside it
References
- Full API Reference: See references/api-reference.md for node types (35), methods, properties, text API, components
- Variables API: See references/variables-api.md for design tokens — collections, modes, binding, scopes
- Editors & Codegen: See references/editors-codegen.md for FigJam, Slides, Buzz, Dev Mode, codegen, dev resources, annotations
- Publishing: See references/publishing.md for review, payments setup, distribution
Debugging
console.log('Debug:', value);
figma.notify('Status message');
figma.notify('Error', { error: true, timeout: 5000 });