12 KiB
Plugin API Patterns
Part of the use_figma skill. Quick reference for common Figma Plugin API operations.
Contents
- Execution Basics
- Creating Nodes
- Fills and Strokes
- Auto Layout
- Effects
- Opacity and Blend Modes
- Corner Radius and Clipping
- Grouping and Organization
- Components and Variants
- Styles
- Cloning, Finding Nodes, and Grids
- Constraints and Viewport
Execution Basics
Page Context
Page context resets between use_figma calls — figma.currentPage always starts on the first page. Use await figma.setCurrentPageAsync(page) at the start of each invocation to switch to the correct page.
const targetPage = figma.root.children.find(p => p.name === "My Page");
await figma.setCurrentPageAsync(targetPage);
// targetPage.children is now populated
Returning Results
Scripts are automatically wrapped in an async IIFE with error handling. Just write plain JS and use return to send data back to the agent:
// Return an object — auto-serialized to JSON
return { nodeId: frame.id, count: 5 }
// Return a string
return "Created 3 components"
Errors are automatically captured — no try/catch needed. figma.notify() does not exist. Return all information via the return value.
Working Incrementally
Don't build an entire screen in one call. Break work into small steps:
- Create tokens/variables
- Create text styles
- Build individual components
- Compose sections
- Assemble screens
Verify structure with get_metadata between steps. Use get_screenshot after each major creation milestone to catch visual problems early.
Creating Nodes
Frames
const frame = figma.createFrame();
frame.name = "Container";
frame.resize(1440, 900);
frame.x = 0;
frame.y = 0;
frame.fills = [{ type: "SOLID", color: { r: 0.98, g: 0.98, b: 0.99 } }];
Text
// MUST load font before any text operations
await figma.loadFontAsync({ family: "Inter", style: "Regular" });
const text = figma.createText();
text.fontName = { family: "Inter", style: "Regular" };
text.fontSize = 16;
text.lineHeight = { value: 24, unit: "PIXELS" };
text.letterSpacing = { value: 0, unit: "PERCENT" };
text.characters = "Hello World";
text.fills = [{ type: "SOLID", color: { r: 0.1, g: 0.1, b: 0.12 } }];
Rectangles
const rect = figma.createRectangle();
rect.name = "Background";
rect.resize(400, 300);
rect.cornerRadius = 12;
rect.fills = [{ type: "SOLID", color: { r: 0.95, g: 0.95, b: 0.96 } }];
Ellipses
const circle = figma.createEllipse();
circle.name = "Avatar Circle";
circle.resize(48, 48);
circle.fills = [{ type: "SOLID", color: { r: 0.85, g: 0.87, b: 0.90 } }];
Lines
const line = figma.createLine();
line.name = "Divider";
line.resize(400, 0);
line.strokes = [{ type: "SOLID", color: { r: 0, g: 0, b: 0 }, opacity: 0.08 }];
line.strokeWeight = 1;
SVG Import
const svgString = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 12h14M12 5l7 7-7 7" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`;
const node = figma.createNodeFromSvg(svgString);
node.name = "Icon/Arrow Right";
node.resize(24, 24);
Fills & Strokes
Solid Fill
node.fills = [{ type: "SOLID", color: { r: 0.2, g: 0.2, b: 0.25 } }];
Fill with Opacity
node.fills = [{ type: "SOLID", color: { r: 0.2, g: 0.2, b: 0.25 }, opacity: 0.5 }];
No Fill (Transparent)
node.fills = [];
Linear Gradient
node.fills = [{
type: "GRADIENT_LINEAR",
gradientStops: [
{ color: { r: 0.2, g: 0.36, b: 0.96, a: 1 }, position: 0 },
{ color: { r: 0.56, g: 0.24, b: 0.88, a: 1 }, position: 1 }
],
gradientTransform: [[1, 0, 0], [0, 1, 0]]
}];
Strokes
node.strokes = [{ type: "SOLID", color: { r: 0.85, g: 0.85, b: 0.87 } }];
node.strokeWeight = 1;
node.strokeAlign = "INSIDE"; // "CENTER", "OUTSIDE"
Multiple Fills (Layered)
node.fills = [
{ type: "SOLID", color: { r: 0.95, g: 0.95, b: 0.96 } },
{ type: "SOLID", color: { r: 0.2, g: 0.36, b: 0.96 }, opacity: 0.05 }
];
Auto Layout
Setting Up Auto Layout
const frame = figma.createFrame();
frame.layoutMode = "VERTICAL"; // or "HORIZONTAL"
frame.primaryAxisSizingMode = "AUTO"; // Hug main axis
frame.counterAxisSizingMode = "FIXED"; // Fixed cross axis
frame.resize(360, 1); // Width fixed, height auto
frame.itemSpacing = 16; // Gap between children
frame.paddingTop = 24;
frame.paddingBottom = 24;
frame.paddingLeft = 24;
frame.paddingRight = 24;
Alignment
// Main axis (direction of layout)
frame.primaryAxisAlignItems = "MIN"; // Start
frame.primaryAxisAlignItems = "CENTER"; // Center
frame.primaryAxisAlignItems = "MAX"; // End
frame.primaryAxisAlignItems = "SPACE_BETWEEN"; // Distribute
// Cross axis
frame.counterAxisAlignItems = "MIN"; // Start
frame.counterAxisAlignItems = "CENTER"; // Center
frame.counterAxisAlignItems = "MAX"; // End
// NOTE: 'STRETCH' is NOT valid — use 'MIN' + child.layoutSizingX = 'FILL'
Child Sizing
// IMPORTANT: FILL can only be set AFTER the child is appended to an auto-layout parent
parent.appendChild(child)
child.layoutSizingHorizontal = "FILL"; // Stretch to parent
child.layoutSizingHorizontal = "HUG"; // Shrink to content
child.layoutSizingHorizontal = "FIXED"; // Manual width
child.layoutSizingVertical = "FILL";
child.layoutSizingVertical = "HUG";
child.layoutSizingVertical = "FIXED";
Wrapping (Grid-like Layout)
frame.layoutMode = "HORIZONTAL";
frame.layoutWrap = "WRAP";
frame.itemSpacing = 24; // Horizontal gap
frame.counterAxisSpacing = 24; // Vertical gap (between rows)
Absolute Positioning Within Auto Layout
child.layoutPositioning = "ABSOLUTE";
child.constraints = { horizontal: "MAX", vertical: "MIN" }; // Top-right
child.x = parentWidth - childWidth - 8;
child.y = 8;
Effects
Drop Shadow
node.effects = [{
type: "DROP_SHADOW",
color: { r: 0, g: 0, b: 0, a: 0.08 },
offset: { x: 0, y: 4 },
radius: 16,
spread: -2,
visible: true,
blendMode: "NORMAL"
}];
Inner Shadow
node.effects = [{
type: "INNER_SHADOW",
color: { r: 0, g: 0, b: 0, a: 0.05 },
offset: { x: 0, y: 1 },
radius: 2,
spread: 0,
visible: true,
blendMode: "NORMAL"
}];
Background Blur
node.effects = [{
type: "BACKGROUND_BLUR",
radius: 16,
visible: true
}];
Layer Blur
node.effects = [{
type: "LAYER_BLUR",
radius: 8,
visible: true
}];
Multiple Effects
node.effects = [
{ type: "DROP_SHADOW", color: { r: 0, g: 0, b: 0, a: 0.04 }, offset: { x: 0, y: 1 }, radius: 3, spread: 0, visible: true, blendMode: "NORMAL" },
{ type: "DROP_SHADOW", color: { r: 0, g: 0, b: 0, a: 0.06 }, offset: { x: 0, y: 8 }, radius: 24, spread: -4, visible: true, blendMode: "NORMAL" }
];
Opacity & Blend Modes
node.opacity = 0.5;
node.blendMode = "NORMAL"; // "MULTIPLY", "SCREEN", "OVERLAY", "DARKEN", "LIGHTEN", etc.
Corner Radius
// Uniform
node.cornerRadius = 12;
// Per-corner
node.topLeftRadius = 12;
node.topRightRadius = 12;
node.bottomLeftRadius = 0;
node.bottomRightRadius = 0;
Clipping
frame.clipsContent = true; // Children clipped to frame bounds
Grouping & Organization
Groups
const group = figma.group([node1, node2, node3], figma.currentPage);
group.name = "Grouped Elements";
Sections
const section = figma.createSection();
section.name = "My Section";
section.resizeWithoutConstraints(800, 600);
section.x = 0;
section.y = 0;
// IMPORTANT: Sections don't auto-resize — always resize after adding content
Appending Children
parentFrame.appendChild(childNode);
// Insert at a specific index
parentFrame.insertChild(0, childNode); // Insert at beginning
Components & Variants
Create Component
const component = figma.createComponent();
component.name = "Button/Primary";
component.description = "Primary action button.";
Create Instance
const instance = component.createInstance();
instance.x = 200;
instance.y = 100;
Import Components by Key (Team Libraries)
These methods import components from team libraries (not the same file). For components in the current file, use figma.getNodeByIdAsync() or findOne()/findAll().
// Import a published component from a team library by its key
const comp = await figma.importComponentByKeyAsync(componentKey)
const instance = comp.createInstance()
// Import a published component set from a team library by its key
const set = await figma.importComponentSetByKeyAsync(componentSetKey)
const variant = set.defaultVariant
const variantInstance = variant.createInstance()
Combine as Variants
// IMPORTANT: Pass ComponentNodes (not frames)
const componentSet = figma.combineAsVariants(
[variantA, variantB, variantC],
figma.currentPage
);
componentSet.name = "Button";
componentSet.description = "Button component with multiple variants.";
// CRITICAL: Layout variants in a grid after combining (they stack at 0,0)
let maxX = 0, maxY = 0;
componentSet.children.forEach((child, i) => {
child.x = (i % numCols) * colWidth;
child.y = Math.floor(i / numCols) * rowHeight;
});
for (const child of componentSet.children) {
maxX = Math.max(maxX, child.x + child.width);
maxY = Math.max(maxY, child.y + child.height);
}
componentSet.resizeWithoutConstraints(maxX + 40, maxY + 40);
Component Properties
// addComponentProperty returns a STRING key — capture it!
const labelKey = component.addComponentProperty("label", "TEXT", "Button");
const showIconKey = component.addComponentProperty("showIcon", "BOOLEAN", true);
const iconSlotKey = component.addComponentProperty("iconSlot", "INSTANCE_SWAP", defaultIconId);
// MUST link properties to child nodes via componentPropertyReferences
labelNode.componentPropertyReferences = { characters: labelKey };
iconInstance.componentPropertyReferences = {
visible: showIconKey,
mainComponent: iconSlotKey
};
Styles
Text Style
await figma.loadFontAsync({ family: "Inter", style: "Regular" });
const style = figma.createTextStyle();
style.name = "Body/Default";
style.fontName = { family: "Inter", style: "Regular" };
style.fontSize = 16;
style.lineHeight = { value: 24, unit: "PIXELS" };
style.letterSpacing = { value: 0, unit: "PERCENT" };
// Apply to a text node
textNode.textStyleId = style.id;
Effect Style
const shadowStyle = figma.createEffectStyle();
shadowStyle.name = "Shadow/Subtle";
shadowStyle.effects = [{
type: "DROP_SHADOW",
color: { r: 0, g: 0, b: 0, a: 0.06 },
offset: { x: 0, y: 2 },
radius: 8,
spread: 0,
visible: true,
blendMode: "NORMAL"
}];
// Apply to a node
frame.effectStyleId = shadowStyle.id;
Cloning & Duplication
const clone = originalNode.clone();
clone.x = originalNode.x + originalNode.width + 40;
clone.name = "Copy of " + originalNode.name;
Finding Nodes
// Find by name on current page
const node = figma.currentPage.findOne(n => n.name === "My Frame");
// Find all by type
const allTexts = figma.currentPage.findAll(n => n.type === "TEXT");
// Find all by name pattern
const allButtons = figma.currentPage.findAll(n => n.name.startsWith("Button/"));
Layout Grids
frame.layoutGrids = [
{
pattern: "COLUMNS",
alignment: "STRETCH",
count: 12,
gutterSize: 24,
offset: 80,
visible: true
}
];
Constraints (Non-Auto-Layout Frames)
child.constraints = {
horizontal: "LEFT_RIGHT", // LEFT, RIGHT, CENTER, LEFT_RIGHT, SCALE
vertical: "TOP" // TOP, BOTTOM, CENTER, TOP_BOTTOM, SCALE
};
Viewport & Zoom
// Zoom to fit specific nodes
figma.viewport.scrollAndZoomIntoView([frame1, frame2]);