How to Build Figma Integration Plugins with the use_figma Capability

You build Figma integration plugins by loading the figma-use skill and sending JavaScript code blocks to the use_figma tool, which executes them inside Figma files and returns only JSON-serialized return values.

The use_figma capability in the openai/plugins repository provides a Codex-backed plugin architecture that wraps the official Figma Plugin API. This integration enables AI assistants to programmatically create nodes, edit design systems, and manipulate files through a secure runtime environment. Understanding the three-layer architecture and strict execution rules is essential for writing reliable scripts that interact with Figma files.

Architecture of the use_figma Capability

The Figma integration is structured as a multi-layered system that separates configuration, skill definitions, and runtime execution.

Plugin Manifest and Skill Structure

The first layer resides in plugins/figma/.codex-plugin/plugin.json, which defines the plugin to Codex and specifies which app to load. The second layer consists of skill packages located in plugins/figma/skills/figma-use/. Each skill is a self-contained bundle containing:

  • SKILL.md – Metadata and execution rules
  • references/ – Documentation and API references
  • Optional agents/, assets/, and scripts/ directories

The figma-use skill serves as the core entry point for any script that needs to run JavaScript inside a Figma file.

Runtime Execution Model

When the assistant calls use_figma, the runtime injects the supplied script into a temporary plugin running within the target file. The execution follows five strict stages:

  1. Skill Loading – The assistant must load the figma-use skill before any call to set up the environment.
  2. Tool Invocation – The assistant sends a JSON-encoded block specifying "tool": "use_figma" and the JavaScript code.
  3. Automatic Wrapping – The runtime adds an async wrapper and injects top-level await. You must not wrap your code in an IIFE.
  4. Return-Only Communication – Only the final return value is serialized and sent back; console.log and figma.notify are ignored.
  5. Atomic Execution – If the script throws an error, the file remains untouched with no partial changes applied.

Critical Rules for use_figma Scripts

Scripts executing under the use_figma capability must adhere to specific constraints to ensure safe file manipulation.

Communication via Return Values

Every script must end with a top-level return statement containing a JSON-serializable object. You cannot rely on standard output or UI notifications to convey information back to the assistant.

// Correct: Returns data explicitly
const rect = figma.createRectangle();
return { createdNodeIds: [rect.id] };

// Incorrect: console.log is silently discarded
console.log("Created rectangle");

Incremental Workflow Constraints

Limit each use_figma call to at most 10 logical operations. Split larger workflows into multiple sequential calls to avoid timeouts and simplify debugging. Each call may touch only one page, so page switching requires separate invocations.

Error Handling and Atomicity

All changes within a single call are atomic. If any part of the script throws, the entire operation rolls back, leaving the Figma file unchanged. This prevents corrupted states during complex multi-node operations.

Core Concepts for Figma File Manipulation

Mastering these patterns ensures your scripts handle Figma's asynchronous API and node hierarchy correctly.

figma.currentPage always starts on the first page of the file. Switch pages only with await figma.setCurrentPageAsync(page) and never inside loops.

// Find and switch to the Design System page
const targetPage = figma.root.children.find(p => p.name === "Design System");
await figma.setCurrentPageAsync(targetPage);
return { pageId: targetPage.id };

The Canonical Text-Edit Recipe

Text mutations require explicit font loading to prevent "Cannot write to node with unloaded font" errors. Always follow this sequence:

const txt = await figma.getNodeByIdAsync(textId);
if (txt.type !== "TEXT") throw new Error("Node is not a text node");

// Load fonts before mutation
await txt.loadFontAsync();
txt.characters = "Updated text";

return { mutatedNodeIds: [txt.id] };

Auto-Layout and Node Creation

Always create containers with figma.createAutoLayout before adding children. This guarantees that layoutSizingHorizontal/Vertical = 'FILL' works correctly. The skill provides helper methods like node.query and node.set to reduce boilerplate.

Practical use_figma Code Examples

These snippets demonstrate patterns from plugins/figma/skills/figma-use/SKILL.md that handle common tasks.

Creating Rectangles and Returning Node IDs

Create a red rectangle at specific coordinates and return its identifier for future reference:

const rect = figma.createRectangle();
rect.resize(200, 120);
rect.fills = [{type: "SOLID", color: {r: 1, g: 0, b: 0}}];
rect.x = 100;
rect.y = 100;
figma.currentPage.appendChild(rect);

return { createdNodeIds: [rect.id] };

Querying Nodes with CSS-like Selectors

Use the node.query API to find and batch-modify nodes matching specific patterns:

// Find all rectangles ending with "Button" and reduce opacity
const buttons = figma.currentPage.query('RECTANGLE[name$=Button]');
buttons.set({ opacity: 0.6 });

return { mutatedNodeIds: buttons.map(n => n.id) };

Working with Components and Instances

Switch to a non-default page and instantiate a component:

const targetPage = figma.root.children.find(p => p.name === "Design System");
await figma.setCurrentPageAsync(targetPage);

const container = figma.createAutoLayout("VERTICAL", { name: "Cards", itemSpacing: 12 });
figma.currentPage.appendChild(container);

const comp = await figma.getNodeByIdAsync("123:456");
const instance = comp.createInstance();
container.appendChild(instance);

return {
  createdNodeIds: [container.id, instance.id],
  pageId: targetPage.id
};

Capturing Inline Screenshots

Generate PNG previews of specific frames after modifications:

const frame = figma.currentPage.query('FRAME[name=Header]').first();
await frame.screenshot({ scale: 2 });

return { screenshotNodeId: frame.id };

Essential Reference Files in openai/plugins

Consult these source files for detailed specifications and troubleshooting:

Summary

  • Load the figma-use skill before any use_figma invocation to initialize the execution environment.
  • Use top-level await and a single return statement; never wrap code in IIFEs or use console.log for output.
  • Return node IDs for all created or mutated elements to enable subsequent script references.
  • Follow the canonical text-edit recipe (loadFontAsync before mutation) to prevent font loading errors.
  • Limit each call to 10 logical operations and split workflows across multiple use_figma calls for reliability.
  • Switch pages only via await figma.setCurrentPageAsync(page) and verify with returned metadata or screenshots.

Frequently Asked Questions

What is the use_figma capability?

The use_figma capability is a tool in the openai/plugins repository that allows AI assistants to execute JavaScript inside Figma files through a controlled runtime. It acts as a bridge between Codex and the Figma Plugin API, automatically handling async execution and returning only JSON-serialized data from the script's return statement.

How do I handle fonts when editing text in Figma via use_figma?

You must call await txt.loadFontAsync() before modifying the characters property of any text node. This loads all font variants used by the node and prevents write errors. The mutation must occur after the await resolves, as demonstrated in the canonical text-edit recipe in SKILL.md.

Why is my use_figma script timing out?

Scripts timeout when they exceed execution limits or perform too many operations in a single call. Keep each invocation under 10 logical operations, avoid heavy computations, and split complex workflows into multiple sequential use_figma calls. Ensure you are not creating infinite loops or performing blocking synchronous operations.

Can I use console.log for debugging inside use_figma scripts?

No. The runtime ignores console.log, figma.notify, and figma.closePlugin. All communication must flow through the return statement. For debugging, return intermediate state objects or use the node.screenshot() method to capture visual confirmation of the file state.

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 →