Understanding figma-use Skill Critical Rules and API Constraints: A Complete MCP Guide

The figma-use skill enforces strict JavaScript execution rules—including mandatory JSON returns, async/await requirements, and immutable array handling—to ensure atomic, reliable edits via the Figma Plugin API.

The figma-use skill serves as the primary Model-Centered Programming (MCP) interface for executing JavaScript against Figma files within a controlled, headless plugin environment. According to the figma/mcp-server-guide repository, mastering the figma-use skill critical rules and API constraints is essential for building reliable automation that prevents partial state corruption and guarantees atomic transaction failures.

Architecture of the MCP Server Guide

The repository organizes functionality into distinct layers that agents must understand before invoking the use_figma tool. The server definition lives in server.json, while skill definitions reside in skills/figma-use/SKILL.md. Reference documentation covering API surfaces and edge cases is located in skills/figma-use/references/, including the critical gotchas.md file and complete TypeScript definitions in plugin-api-standalone.d.ts.

When an agent loads the figma-use skill, the MCP client executes JavaScript snippets in a headless Figma plugin context. The runtime automatically wraps scripts in an async function and returns only the value from the final return statement, discarding all console.log output.

Critical Rules for Safe Execution

Violating any of these rules results in immediate script termination with atomic rollback—no partial changes persist in the Figma file.

Return JSON data exclusively. All output must flow through the return statement. Using console.log or figma.notify causes errors or silent failures.

Write top-level async code. The runtime automatically wraps the entire script in an async function. Do not add IIFE wrappers or manual async function declarations.

Never use figma.notify. This API is stubbed out in the headless environment and throws a "not implemented" error. Use return to communicate status.

Use shared plugin data only. The methods getPluginData and setPluginData are unavailable. Use getSharedPluginData and setSharedPluginData instead.

Treat fills and strokes as immutable. Arrays returned by node.fills or node.strokes are read-only. Clone the array, modify the copy, then re-assign: node.fills = newFillsArray.

Normalize colors to 0–1 range. All solid color channels use normalized values. Supplying 0–255 integers causes validation errors.

Reset page context awareness. Each use_figma call starts on the first page regardless of previous state. Switch pages explicitly using await figma.setCurrentPageAsync(page).

Observe layout sizing order. Set layoutSizingHorizontal or layoutSizingVertical to 'FILL' only after appending the node to an auto-layout parent.

Explicitly set variable scopes. Always define variable.scopes explicitly. The default ALL_SCOPES value pollutes every variable picker in the Figma UI.

Await every promise. Forgetting await before asynchronous operations like font loading, page switches, or variable binding creates fire-and-forget bugs that return undefined values.

Return all affected node IDs. Scripts must return a structured object containing every created or mutated node ID. Subsequent calls rely on these identifiers for reference.

Respect atomic failure. If a script errors, no changes are applied to the Figma file. Fix the error and retry the entire operation.

API Constraints and Limitations

The figma-use skill operates with specific API constraints documented in skills/figma-use/references/gotchas.md. Understanding these limitations prevents common integration failures.

Forbidden direct page assignment. Assigning figma.currentPage = targetPage throws an error. Use await figma.setCurrentPageAsync(targetPage) instead.

Paint binding returns new objects. The method setBoundVariableForPaint returns a new paint object rather than mutating the input. Capture this return value before assigning to node.fills.

Component property keys are strings. The addComponentProperty method returns a string key, not an object. Store this key to reference in componentPropertyReferences.

Resize resets auto-layout modes. Calling resize() on a node resets primaryAxisSizingMode and counterAxisSizingMode. Call resize before setting these modes, or use reasonable default sizes.

Variable collections start with one mode. New collections created via createVariableCollection initialize with a single default mode named "Mode 1". Rename it immediately using coll.renameMode(coll.modes[0].modeId, 'Light').

Layout grow requires fixed parents. Setting layoutGrow on a child of a hugging parent causes content compression. Only use layoutGrow when the parent container uses FIXED primary axis sizing.

Text style variable binding is unsupported. The setBoundVariable method on TextStyle objects is not available in headless mode. Assign raw values directly to text style properties.

Practical Code Examples

Inspecting File Structure Safely

Read-only inspection scripts must return plain JSON without side effects:

// Read-only inspection of pages
const pages = figma.root.children.map(p => ({
  name: p.name,
  id: p.id,
  childCount: p.children.length
}));
return pages;

This approach uses only read operations and returns a serializable array, satisfying the rule to return JSON data.

Creating Variable Collections with Multiple Modes

Design systems require explicit mode management. New collections start with a single mode that requires renaming:

// Create collection and configure modes
const coll = figma.variables.createVariableCollection('Colors');

// Rename default mode from "Mode 1" to "Light"
coll.renameMode(coll.modes[0].modeId, 'Light');

// Add secondary mode
const darkModeId = coll.addMode('Dark');

// Return all IDs for subsequent operations
return {
  collectionId: coll.id,
  lightModeId: coll.modes[0].modeId,
  darkModeId
};

Note that await is unnecessary here because these synchronous methods return immediately.

Positioning Nodes to Prevent Overlap

New nodes default to coordinates (0, 0), causing them to stack on existing content. Calculate positioning dynamically:

// Find rightmost edge on current page
let maxX = 0;
for (const child of figma.currentPage.children) {
  const right = child.x + child.width;
  if (right > maxX) maxX = right;
}

// Create frame with offset
const frame = figma.createFrame();
frame.name = 'New Card';
frame.resize(400, 300);
figma.currentPage.appendChild(frame);
frame.x = maxX + 100;
frame.y = 0;

return { createdNodeIds: [frame.id] };

Returning the created node ID allows subsequent scripts to target this specific frame.

Binding Color Variables to Shapes

Variable binding requires careful handling of paint objects and normalized color values:

// Setup collection and variable
const coll = figma.variables.createVariableCollection('Palette');
coll.renameMode(coll.modes[0].modeId, 'Default');
const colorVar = figma.variables.createVariable('Primary', coll, 'COLOR');
colorVar.setValueForMode(coll.modes[0].modeId, { r: 0.2, g: 0.5, b: 0.8, a: 1 });

// Create shape
const rect = figma.createRectangle();
rect.resize(200, 100);
figma.currentPage.appendChild(rect);

// Bind variable to fill (returns new paint object)
const basePaint = { type: 'SOLID', color: { r: 0, g: 0, b: 0 } };
const boundPaint = figma.variables.setBoundVariableForPaint(basePaint, 'color', colorVar);
rect.fills = [boundPaint];

return {
  createdNodeIds: [rect.id],
  variableId: colorVar.id,
  collectionId: coll.id
};

Colors use the normalized 0–1 range, and setBoundVariableForPaint generates a new paint instance that must be captured.

Switching pages requires the async setter, and auto-layout properties must be set in specific order:

// Switch to non-first page
const targetPage = figma.root.children.find(p => p.name === 'Components');
await figma.setCurrentPageAsync(targetPage);

// Create auto-layout container
const auto = figma.createFrame();
auto.layoutMode = 'VERTICAL';
auto.primaryAxisSizingMode = 'AUTO';
auto.counterAxisSizingMode = 'FIXED';
auto.resize(400, 1);

// Append child BEFORE setting FILL sizing
const child = figma.createRectangle();
child.resize(380, 80);
auto.appendChild(child);
auto.layoutSizingHorizontal = 'FILL';

figma.currentPage.appendChild(auto);
return { createdNodeIds: [auto.id, child.id] };

Setting layoutSizingHorizontal only works after the node has a parent with auto-layout enabled.

Key Reference Files

The following files in figma/mcp-server-guide provide authoritative guidance:

Summary

  • The figma-use skill requires returning JSON onlyconsole.log and figma.notify are unsupported or throw errors.
  • Atomic failure guarantees that script errors leave the Figma file unchanged, preventing partial corruption.
  • Immutable arrays for fills and strokes require cloning before modification.
  • Normalized colors (0–1 range) are mandatory; integer RGB values cause validation failures.
  • Explicit page switching via setCurrentPageAsync is required because each call resets to the first page.
  • Auto-layout constraints dictate that layoutSizingHorizontal/Vertical must be set after appending to a parent container.

Frequently Asked Questions

What happens if I forget to return JSON in a figma-use script?

The script executes but returns no data to the MCP client, making it impossible to verify success or obtain node IDs for subsequent operations. Per SKILL.md rule 1, always use return to output structured data.

Why does figma.notify throw an error in figma-use?

The headless plugin environment used by the MCP server does not implement UI notifications. The API is stubbed and throws a "not implemented" error. Use return to communicate completion status or data back to the agent.

How do I handle color values when using the figma-use skill?

All color channels must use normalized values between 0 and 1. For example, white is { r: 1, g: 1, b: 1 }, not { r: 255, g: 255, b: 255 }. Supplying integers in the 0–255 range triggers validation errors as documented in gotchas.md.

What is the correct order for setting auto-layout properties?

First append the child node to an auto-layout parent, then set layoutSizingHorizontal or layoutSizingVertical to 'FILL'. Setting these properties before establishing the parent-child relationship fails silently because the system cannot validate the layout context.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →