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 rulesreferences/– Documentation and API references- Optional
agents/,assets/, andscripts/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:
- Skill Loading – The assistant must load the
figma-useskill before any call to set up the environment. - Tool Invocation – The assistant sends a JSON-encoded block specifying
"tool": "use_figma"and the JavaScript code. - Automatic Wrapping – The runtime adds an
asyncwrapper and injects top-levelawait. You must not wrap your code in an IIFE. - Return-Only Communication – Only the final
returnvalue is serialized and sent back;console.logandfigma.notifyare ignored. - 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.
Page Navigation and Context
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:
plugins/figma/.codex-plugin/plugin.json– Plugin manifest defining the Figma app integrationplugins/figma/skills/figma-use/SKILL.md– Core skill definition, execution rules, and best-practice checklistplugins/figma/skills/figma-use/references/gotchas.md– Common failure modes and correct/incorrect code comparisonsplugins/figma/skills/figma-use/references/api-reference.md– Full auto-generated typings for the Plugin APIplugins/figma/skills/figma-generate-design/SKILL.md– Multi-page screen generation skill (optional companion tofigma-use)plugins/figma/skills/figma-generate-library/SKILL.md– Component library and token binding generation skill
Summary
- Load the
figma-useskill before anyuse_figmainvocation to initialize the execution environment. - Use top-level
awaitand a singlereturnstatement; never wrap code in IIFEs or useconsole.logfor output. - Return node IDs for all created or mutated elements to enable subsequent script references.
- Follow the canonical text-edit recipe (
loadFontAsyncbefore mutation) to prevent font loading errors. - Limit each call to 10 logical operations and split workflows across multiple
use_figmacalls 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →