Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Official Figma skill for writing directly to the Figma canvas through the MCP server and Plugin API.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
plugin-api-patterns.md
1# Plugin API Patterns23> Part of the [use_figma skill](../SKILL.md). Quick reference for common Figma Plugin API operations.45## Contents67- Execution Basics8- Creating Nodes9- Fills and Strokes10- Auto Layout11- Effects12- Opacity and Blend Modes13- Corner Radius and Clipping14- Grouping and Organization15- Components and Variants16- Styles17- Cloning, Finding Nodes, and Grids18- Constraints and Viewport192021## Execution Basics2223### Page Context2425Page 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.2627```javascript28const targetPage = figma.root.children.find(p => p.name === "My Page");29await figma.setCurrentPageAsync(targetPage);30// targetPage.children is now populated31```3233### Returning Results3435Scripts are automatically wrapped in an async IIFE with error handling. Just write plain JS and use `return` to send data back to the agent:3637```javascript38// Return an object — auto-serialized to JSON39return { nodeId: frame.id, count: 5 }4041// Return a string42return "Created 3 components"43```4445Errors are automatically captured — no try/catch needed. `figma.notify()` does **not** exist. Return all information via the `return` value.4647### Working Incrementally4849Don't build an entire screen in one call. Break work into small steps:501. Create tokens/variables512. Create text styles523. Build individual components534. Compose sections545. Assemble screens5556Verify structure with `get_metadata` between steps. Use `get_screenshot` after each major creation milestone to catch visual problems early.5758## Creating Nodes5960### Frames6162```javascript63const frame = figma.createFrame();64frame.name = "Container";65frame.resize(1440, 900);66frame.x = 0;67frame.y = 0;68frame.fills = [{ type: "SOLID", color: { r: 0.98, g: 0.98, b: 0.99 } }];69```7071### Text7273```javascript74// MUST load font before any text operations75await figma.loadFontAsync({ family: "Inter", style: "Regular" });7677const text = figma.createText();78text.fontName = { family: "Inter", style: "Regular" };79text.fontSize = 16;80text.lineHeight = { value: 24, unit: "PIXELS" };81text.letterSpacing = { value: 0, unit: "PERCENT" };82text.characters = "Hello World";83text.fills = [{ type: "SOLID", color: { r: 0.1, g: 0.1, b: 0.12 } }];84```8586### Rectangles8788```javascript89const rect = figma.createRectangle();90rect.name = "Background";91rect.resize(400, 300);92rect.cornerRadius = 12;93rect.fills = [{ type: "SOLID", color: { r: 0.95, g: 0.95, b: 0.96 } }];94```9596### Ellipses9798```javascript99const circle = figma.createEllipse();100circle.name = "Avatar Circle";101circle.resize(48, 48);102circle.fills = [{ type: "SOLID", color: { r: 0.85, g: 0.87, b: 0.90 } }];103```104105### Lines106107```javascript108const line = figma.createLine();109line.name = "Divider";110line.resize(400, 0);111line.strokes = [{ type: "SOLID", color: { r: 0, g: 0, b: 0 }, opacity: 0.08 }];112line.strokeWeight = 1;113```114115### SVG Import116117```javascript118const svgString = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">119<path d="M5 12h14M12 5l7 7-7 7" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>120</svg>`;121122const node = figma.createNodeFromSvg(svgString);123node.name = "Icon/Arrow Right";124node.resize(24, 24);125```126127## Fills & Strokes128129### Solid Fill130131```javascript132node.fills = [{ type: "SOLID", color: { r: 0.2, g: 0.2, b: 0.25 } }];133```134135### Fill with Opacity136137```javascript138node.fills = [{ type: "SOLID", color: { r: 0.2, g: 0.2, b: 0.25 }, opacity: 0.5 }];139```140141### No Fill (Transparent)142143```javascript144node.fills = [];145```146147### Linear Gradient148149```javascript150node.fills = [{151type: "GRADIENT_LINEAR",152gradientStops: [153{ color: { r: 0.2, g: 0.36, b: 0.96, a: 1 }, position: 0 },154{ color: { r: 0.56, g: 0.24, b: 0.88, a: 1 }, position: 1 }155],156gradientTransform: [[1, 0, 0], [0, 1, 0]]157}];158```159160### Strokes161162```javascript163node.strokes = [{ type: "SOLID", color: { r: 0.85, g: 0.85, b: 0.87 } }];164node.strokeWeight = 1;165node.strokeAlign = "INSIDE"; // "CENTER", "OUTSIDE"166```167168### Multiple Fills (Layered)169170```javascript171node.fills = [172{ type: "SOLID", color: { r: 0.95, g: 0.95, b: 0.96 } },173{ type: "SOLID", color: { r: 0.2, g: 0.36, b: 0.96 }, opacity: 0.05 }174];175```176177## Auto Layout178179### Setting Up Auto Layout180181```javascript182const frame = figma.createFrame();183frame.layoutMode = "VERTICAL"; // or "HORIZONTAL"184frame.primaryAxisSizingMode = "AUTO"; // Hug main axis185frame.counterAxisSizingMode = "FIXED"; // Fixed cross axis186frame.resize(360, 1); // Width fixed, height auto187frame.itemSpacing = 16; // Gap between children188frame.paddingTop = 24;189frame.paddingBottom = 24;190frame.paddingLeft = 24;191frame.paddingRight = 24;192```193194### Alignment195196```javascript197// Main axis (direction of layout)198frame.primaryAxisAlignItems = "MIN"; // Start199frame.primaryAxisAlignItems = "CENTER"; // Center200frame.primaryAxisAlignItems = "MAX"; // End201frame.primaryAxisAlignItems = "SPACE_BETWEEN"; // Distribute202203// Cross axis204frame.counterAxisAlignItems = "MIN"; // Start205frame.counterAxisAlignItems = "CENTER"; // Center206frame.counterAxisAlignItems = "MAX"; // End207// NOTE: 'STRETCH' is NOT valid — use 'MIN' + child.layoutSizingX = 'FILL'208```209210### Child Sizing211212```javascript213// IMPORTANT: FILL can only be set AFTER the child is appended to an auto-layout parent214parent.appendChild(child)215child.layoutSizingHorizontal = "FILL"; // Stretch to parent216child.layoutSizingHorizontal = "HUG"; // Shrink to content217child.layoutSizingHorizontal = "FIXED"; // Manual width218219child.layoutSizingVertical = "FILL";220child.layoutSizingVertical = "HUG";221child.layoutSizingVertical = "FIXED";222```223224### Wrapping (Grid-like Layout)225226```javascript227frame.layoutMode = "HORIZONTAL";228frame.layoutWrap = "WRAP";229frame.itemSpacing = 24; // Horizontal gap230frame.counterAxisSpacing = 24; // Vertical gap (between rows)231```232233### Absolute Positioning Within Auto Layout234235```javascript236child.layoutPositioning = "ABSOLUTE";237child.constraints = { horizontal: "MAX", vertical: "MIN" }; // Top-right238child.x = parentWidth - childWidth - 8;239child.y = 8;240```241242## Effects243244### Drop Shadow245246```javascript247node.effects = [{248type: "DROP_SHADOW",249color: { r: 0, g: 0, b: 0, a: 0.08 },250offset: { x: 0, y: 4 },251radius: 16,252spread: -2,253visible: true,254blendMode: "NORMAL"255}];256```257258### Inner Shadow259260```javascript261node.effects = [{262type: "INNER_SHADOW",263color: { r: 0, g: 0, b: 0, a: 0.05 },264offset: { x: 0, y: 1 },265radius: 2,266spread: 0,267visible: true,268blendMode: "NORMAL"269}];270```271272### Background Blur273274```javascript275node.effects = [{276type: "BACKGROUND_BLUR",277radius: 16,278visible: true279}];280```281282### Layer Blur283284```javascript285node.effects = [{286type: "LAYER_BLUR",287radius: 8,288visible: true289}];290```291292### Multiple Effects293294```javascript295node.effects = [296{ 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" },297{ 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" }298];299```300301## Opacity & Blend Modes302303```javascript304node.opacity = 0.5;305node.blendMode = "NORMAL"; // "MULTIPLY", "SCREEN", "OVERLAY", "DARKEN", "LIGHTEN", etc.306```307308## Corner Radius309310```javascript311// Uniform312node.cornerRadius = 12;313314// Per-corner315node.topLeftRadius = 12;316node.topRightRadius = 12;317node.bottomLeftRadius = 0;318node.bottomRightRadius = 0;319```320321## Clipping322323```javascript324frame.clipsContent = true; // Children clipped to frame bounds325```326327## Grouping & Organization328329### Groups330331```javascript332const group = figma.group([node1, node2, node3], figma.currentPage);333group.name = "Grouped Elements";334```335336### Sections337338```javascript339const section = figma.createSection();340section.name = "My Section";341section.resizeWithoutConstraints(800, 600);342section.x = 0;343section.y = 0;344// IMPORTANT: Sections don't auto-resize — always resize after adding content345```346347### Appending Children348349```javascript350parentFrame.appendChild(childNode);351352// Insert at a specific index353parentFrame.insertChild(0, childNode); // Insert at beginning354```355356## Components & Variants357358### Create Component359360```javascript361const component = figma.createComponent();362component.name = "Button/Primary";363component.description = "Primary action button.";364```365366### Create Instance367368```javascript369const instance = component.createInstance();370instance.x = 200;371instance.y = 100;372```373374### Import Components by Key (Team Libraries)375376These methods import components from **team libraries** (not the same file). For components in the current file, use `figma.getNodeByIdAsync()` or `findOne()`/`findAll()`.377378```javascript379// Import a published component from a team library by its key380const comp = await figma.importComponentByKeyAsync(componentKey)381const instance = comp.createInstance()382383// Import a published component set from a team library by its key384const set = await figma.importComponentSetByKeyAsync(componentSetKey)385const variant = set.defaultVariant386const variantInstance = variant.createInstance()387```388389### Combine as Variants390391```javascript392// IMPORTANT: Pass ComponentNodes (not frames)393const componentSet = figma.combineAsVariants(394[variantA, variantB, variantC],395figma.currentPage396);397componentSet.name = "Button";398componentSet.description = "Button component with multiple variants.";399400// CRITICAL: Layout variants in a grid after combining (they stack at 0,0)401let maxX = 0, maxY = 0;402componentSet.children.forEach((child, i) => {403child.x = (i % numCols) * colWidth;404child.y = Math.floor(i / numCols) * rowHeight;405});406for (const child of componentSet.children) {407maxX = Math.max(maxX, child.x + child.width);408maxY = Math.max(maxY, child.y + child.height);409}410componentSet.resizeWithoutConstraints(maxX + 40, maxY + 40);411```412413### Component Properties414415```javascript416// addComponentProperty returns a STRING key — capture it!417const labelKey = component.addComponentProperty("label", "TEXT", "Button");418const showIconKey = component.addComponentProperty("showIcon", "BOOLEAN", true);419const iconSlotKey = component.addComponentProperty("iconSlot", "INSTANCE_SWAP", defaultIconId);420421// MUST link properties to child nodes via componentPropertyReferences422labelNode.componentPropertyReferences = { characters: labelKey };423iconInstance.componentPropertyReferences = {424visible: showIconKey,425mainComponent: iconSlotKey426};427```428429## Styles430431### Text Style432433```javascript434await figma.loadFontAsync({ family: "Inter", style: "Regular" });435436const style = figma.createTextStyle();437style.name = "Body/Default";438style.fontName = { family: "Inter", style: "Regular" };439style.fontSize = 16;440style.lineHeight = { value: 24, unit: "PIXELS" };441style.letterSpacing = { value: 0, unit: "PERCENT" };442443// Apply to a text node444textNode.textStyleId = style.id;445```446447### Effect Style448449```javascript450const shadowStyle = figma.createEffectStyle();451shadowStyle.name = "Shadow/Subtle";452shadowStyle.effects = [{453type: "DROP_SHADOW",454color: { r: 0, g: 0, b: 0, a: 0.06 },455offset: { x: 0, y: 2 },456radius: 8,457spread: 0,458visible: true,459blendMode: "NORMAL"460}];461462// Apply to a node463frame.effectStyleId = shadowStyle.id;464```465466## Cloning & Duplication467468```javascript469const clone = originalNode.clone();470clone.x = originalNode.x + originalNode.width + 40;471clone.name = "Copy of " + originalNode.name;472```473474## Finding Nodes475476```javascript477// Find by name on current page478const node = figma.currentPage.findOne(n => n.name === "My Frame");479480// Find all by type481const allTexts = figma.currentPage.findAll(n => n.type === "TEXT");482483// Find all by name pattern484const allButtons = figma.currentPage.findAll(n => n.name.startsWith("Button/"));485```486487## Layout Grids488489```javascript490frame.layoutGrids = [491{492pattern: "COLUMNS",493alignment: "STRETCH",494count: 12,495gutterSize: 24,496offset: 80,497visible: true498}499];500```501502## Constraints (Non-Auto-Layout Frames)503504```javascript505child.constraints = {506horizontal: "LEFT_RIGHT", // LEFT, RIGHT, CENTER, LEFT_RIGHT, SCALE507vertical: "TOP" // TOP, BOTTOM, CENTER, TOP_BOTTOM, SCALE508};509```510511## Viewport & Zoom512513```javascript514// Zoom to fit specific nodes515figma.viewport.scrollAndZoomIntoView([frame1, frame2]);516```517