Figma access
You work with Figma through one command-line tool. It prints KDL and saves its data files as .kdl: one node per line, with a node's plain fields as key=value on that line, so grep, awk and sed work on it directly. The exceptions are spec, which prints its own one-line-per-node text outline, daemon log, which prints the daemon's log lines as they are, and render, screenshot, image-fills and compare, which save image files (their summary on stdout is still KDL). It has two ways in:
- REST (
file,node,spec,render,image-fills,api): reads any file from its URL with the user's token. Nothing has to be open. Reads are cached locally and retried on rate limits. - Plugin (
exec,screenshot,selection,logs,changes,call): a background daemon talks to the Desktop Bridge plugin running in Figma Desktop. This is the only way to change a file. Your main tool here isexec, which runs Figma Plugin API JavaScript in the file.
references/tips.md holds lessons from real runs, by situation. They are guidance, not rules: read the section for the situation you are in, and adapt it to your case.
Rules
- Never show the token. Do not print, echo or log it; test it with
figma-access api /v1/me. - Look before and after you change anything. Judge every visible result on an image, not on the code that made it.
- Read values, don't guess them; the render decides. Take colors, sizes, spacing, fonts and text from the file, never by eye from an image. When the file's data and the render disagree, the render is what the user sees.
- Change only what the user asked for. Reading is always fine. Never delete or overwrite the user's existing work unless the request says so.
- A timeout is not a failure. Figma cannot stop a running script; check
logsand the file before running it again.
Workflow
1. Set up
<skill>is the directory of thisSKILL.md. Run the tool asbun <skill>/scripts/figma-access.js COMMAND ...; this file writes it asfigma-access COMMAND .... It runs on Bun, not Node: always start it withbun, nevernode, and run any JavaScript or TypeScript helper you write withbuntoo. To shorten the command, define a shell function at the start of each shell call (fa(){ bun <skill>/scripts/figma-access.js "$@"; }; fa ...), not a variable: zsh does not split a variable into words. Quote every URL and node id you pass:?,∧are special to the shell. Ifbunis missing, tell the user to install it from https://bun.sh and stop.- Run
figma-access --helponce, andfigma-access <command> --helpbefore the first use of each command. Exit codes: 0 ok, 1 Figma reported a failure, 2 bad arguments, 3 daemon, plugin or token not available. "Reading the output" inreferences/tips.mdexplains the format and how to search it. - Choose the path from the request, as "Choosing a path" in
references/tips.mddescribes: a URL and a read-only job → REST; any change to the file → plugin; both may be used together.
2a. Connect through REST
- Run
figma-access api /v1/me. On "no token": find where the user set it and load it in the same command, as "Token" inreferences/tips.mddescribes. Ask the user for a token (Figma: Settings > Security > Personal access tokens, file content read) only when none is set anywhere. - Run
figma-access file <url>to see the pages and top-level frames. Pick the node to work on, following "Finding the right node" inreferences/tips.md.
2b. Connect through the plugin
- Run
figma-access status. It starts the daemon when needed.- Files listed: go on.
- No files: run
figma-access files, which waits for the plugin. If it exits with 3, runfigma-access setupand give the user itsstepsin plain words: import the plugin once from the printed manifest path, then run Plugins > Development > Figma Desktop Bridge in the file and keep its window open. Ask the user to tell you when it is running, then runfigma-access filesagain.
- More than one file connected: pass
--file <fileKey>on every command after this.
3. Understand the design
- Read the smallest view that answers the question, outline first and details only where you work:
- structure:
figma-access node <url|key> <id> --outline 2(REST), or the page-outline recipe inreferences/plugin-api.md(plugin); - everything code needs, one line per visible node:
figma-access spec <url|key> <id> -o <file>; for a big frame, save it once withnode ... -o <file>and pass--from <file>to every laterspec, as "Rate limits and the cache" inreferences/tips.mddescribes; - what the user means by "this" or "the selected":
figma-access selection; - how it looks:
figma-access render <key> <id>(REST, exact size) orfigma-access screenshot <id>(plugin), then open the image.
- structure:
- Read the
reportsofnode: fonts, image fills, and how many hidden layers were pruned. Read "Hidden layers" and "Reading a design image" inreferences/tips.md. - For components, variants, variables and styles, read their real structure before you change or copy them, and reuse what exists instead of creating duplicates.
4. Do the job
Implement a design in code. Before you write code, read "From design to code" in references/tips.md: most of the differences that remain after a first build (image crops, fonts, text offsets, page width) are covered there. Write the code where the user or their project says it belongs (the path they name, or the project's own structure); _swe/figma-access/ holds only working artifacts such as renders, saved node files, specs, screenshots and scripts.
- Render the frame at scale 1 as the reference, and read it in parts when it is tall.
- Write the frame's
specto a file, with--originat the corner where the page content starts, and take every value from it. Render images, and what it marksICONorMASKED, instead of rebuilding them. Build auto layout frames from the direction, gap, padding and sizingspecprints rather than from their stored boxes, and place the children of groups and frames without auto layout at their boxes. Do not position every node absolutely at its stored box, by hand or with a generator: that page copies every stale box, breaks when a text runs longer, and cannot adapt to the window. Check every part markedLAYOUT DRIFTagainst the render. - List the fonts with every weight; look in the project for the files of any family that is not public, and ask the user, in one message, for the ones still missing, and for anything else you cannot get from the file. Until they arrive, or when you cannot ask, use a stand-in and say so.
- Leave out device or browser chrome, and let the page adapt to the window: the frame's width is the screen it was drawn for, so backgrounds and dividers span the window and the content column is centred, unless the user asks for a fixed width.
- Build section by section, then measure the page against the reference with
figma-access compare <reference.png> <page> --nodes <node file> --wide <px>: it captures the page, prints the differing pixels per section of the frame with the shift that explains each one, how far each text style and node sits off, and which are missing, writes a difference image, and checks a wider window. With--nodesit takes the sections from the frame's own children and crops a reference that the frame's shadow widened. Look at the capture and the difference image too, and repeat until each section matches. When the user asks for a pixel-perfect or 1:1 result and a skill for that is listed in your session, use it with these renders as the design images.
Change the file. Through the plugin:
- Write the change as Plugin API code and run it with
figma-access exec, followingreferences/plugin-api.mdfor what the API enforces and for placement. Long code goes in a working file (for example under_swe/figma-access/) and runs withexec -f. Alwaysreturnthe ids you need next; add--timeout 30sor more for long work. - For jobs a ready-made plugin routine covers (component sets, lint and accessibility audits, image fills, annotations, FigJam and Slides helpers), use
figma-access call <METHOD> '<json>';figma-access methods <filter>lists the params. - Read the output every time: on
ok #false, fix the cause and remove partial work before retrying; on awarningunderresultAnalysis, check ids and names; for the script's own trace,figma-access logs --since 2m. - Screenshot every node you created or changed (
--scale 2for small ones) and check it against the request. Fix and screenshot again; after three rounds that still miss, tell the user what is left and why.
"Working through the plugin" in references/tips.md covers what usually goes wrong here.
5. Report
Tell the user what you read or changed, with node names and ids, and the paths of the renders, screenshots and files you wrote. Name anything you did not do, every assumption you made (a substitute font, a skipped hidden layer, left-out chrome), and what was not verified.
Troubleshooting
- "Rate limit exceeded" (429): the CLI already waited and retried. Follow "Rate limits and the cache" in
references/tips.md, and wait a minute before the next large read. - Exit 3, no file connected: the plugin is not running in that file, or is still searching (it checks every few seconds). Ask the user to run it again.
figma-access daemon logshows connections as they happen. - Another tool uses the same plugin (such as figma-console-mcp): both work side by side;
figma-access statusshows whether this daemon has the file. - Stale behaviour after the skill was updated: run
figma-access setupagain, ask the user to re-import the manifest in Figma, then runfigma-access daemon restart. - "Plugin disconnected before it answered": the user closed the plugin or the file, or the plugin reloaded. Check the file before you repeat the change.