Or: why the "smart" way to fetch Figma designs turned out to be the expensive way.
TL;DR: The "fetch a lightweight outline first" advice is backwards β it cost almost as much as fetching everything at once, and up to 2Γ more on plain, un-componentized files. The real cost isn't the token bill everyone watches β it's Figma's hard API quota (one bad fetch pattern turns a 50-screen project from half a day into 42 months on a Starter plan) and the invisible tax of an AI agent re-reading everything it's ever fetched, every single turn. Fix the file first. Fetch each screen exactly once.
Jump to:
There's a piece of conventional wisdom floating around every Figma-to-code workflow: don't fetch the whole design at once β grab a lightweight outline first, then pull styling only for the bits you actually need. It sounds efficient. It sounds like something a senior engineer would nod along to.
So I tested it. I measured it, frame by frame, character by character. Turns out it's the wrong default.
On a file built with real components, that "smart" outline-first approach cost almost as much as just fetching everything in one go. On a file built from plain, un-componentized frames, it cost up to twice as much.
The things that actually moved the needle were less obvious:
Here's the full breakdown β think of it as a lab report with opinions.
I pointed Figma's remote MCP server (the tool that lets AI agents talk to Figma) at six frames inside one file:
Every number below is labeled honestly, because "trust me" isn't a methodology:
| Label | What it means |
|---|---|
| Measured | An exact character count from a real tool response |
| Validated | Built by a custom emulator that reproduces Figma's output character-for-character on test cases |
| Modeled | Calculated from measured pieces, with assumptions clearly stated |
Token counts assume roughly 3β3.6 characters per token β a rough industry rule of thumb, not gospel. (The actual harness counts tokens exactly; the ratio is just for quick mental math.)
Building the emulator paid off immediately. By reconstructing Figma's own metadata logic from scratch and comparing it line-by-line to real responses, I found out why the tool decides to show what it shows.
This almost got me. My first scan of the SDS page counted 177 nodes. Then I found out Figma's Plugin API silently skips hidden layers inside component instances by default β turning that on bumped the count to 301. Fifty-eight nodes had been invisible the entire time. If I'd shipped the analysis on that first pass, every downstream number would've been wrong.
Before diving into findings, it helps to know what you're paying for. A get_design_context response β the main call an AI agent makes to understand a screen β is made of three parts:
data-node-id, data-name) that exist purely for traceability, not for rendering anything.
The sneaky part: that "fixed instructions" chunk repeats on every single call. Fetch a screen in 10 pieces, and you pay for that boilerplate 10 times over. That single fact is the seed of nearly every finding below.
| Landing page (plain frames) | SDS About β Simple Design System (real components + Code Connect) | |
|---|---|---|
| Visible nodes | 131 | 243 |
get_design_context size |
30,020 chars (~8β10k tokens) | 17,948 chars (~5β6k tokens) |
| Characters per visible node | 229 | 74 |
Read that again: the component-based page has almost double the number of visible nodes and still costs 40% less. Why? Because with Code Connect wired up, each component instance comes back as a clean reference β <Header>, <Card>, <Button> β with its actual props, instead of the tool reverse-engineering a wall of Tailwind classes to approximate what that component looks like.
The plain-frame page didn't just cost more β it produced worse code. Because its root frame was absolutely positioned, the response was littered with 45 absolutely-positioned elements and 54 pixel-perfect offset classes like top-[3857px]. A nav bar sitting 39 pixels off-canvas came back as left-[39px] right-[-39px] β a snapshot of exactly where that one element happened to sit, not a layout that could ever be responsive.
No clever fetching strategy fixes this. If the file itself is built badly, the output is built badly. Full stop.
This is bad on a single screen. It compounds fast once you're not just paying for one screen, but for the "smart" way of fetching it. β
The advice sounds reasonable: call get_metadata first to get a sparse map of the file β layer IDs, names, types, positions β then selectively pull only the styling you need. Here's what that outline actually costs:
| Landing page | SDS About β Simple Design System | |
|---|---|---|
get_metadata size |
11,859 chars | 17,260 chars |
| As a share of the full response | 40% | 96% |
On the component-based page, the "lightweight" outline was almost the entire size of the full response you were trying to avoid paying for. Digging into why:
I3:1200;2142:12360;2142:11561
Not exactly compact β and every single node in a deeply nested instance carries one.
Figma's own internal guidance tells agents to call get_design_context directly and not to substitute the metadata call for it. The numbers back that up completely. Metadata only earns its keep in two specific situations:
get_design_context call that already got truncated β the server automatically falls back to metadata on its own when a response is too big.
So the "efficient" two-step approach is really a one-and-a-half-step approach that costs more than the one step it was trying to avoid. Naturally, the next instinct is to fetch in smaller pieces instead β which makes things worse, not better. β
Fetching a plain-frame page section-by-section means you pay for: the metadata outline, the same code split into pieces, plus the fixed instructions and a screenshot β repeated on every single call. Modeled from measured data:
| Approach | Total characters | Cost vs. one direct call |
|---|---|---|
| One direct call | 30,020 | 1.0Γ |
| Metadata + 8 section calls | ~48,600 | 1.6Γ, plus 8 screenshots |
| Metadata + 15 section calls | ~57,100 | 1.9Γ, plus 15 screenshots |
Fewer, bigger calls are cheaper β and as the quota section below shows, they save your API call budget even harder than they save tokens.
Splitting calls is a token problem. Code Connect looked like the fix for token bloat generally β but it turns out even the "good" format is carrying dead weight. β
Code Connect output is already leaner than raw Tailwind, but it has a built-in redundancy: every prop gets emitted twice, alongside instance-swap IDs and slot placeholders that never get used downstream:
<Button instanceSwapIconStart="3:130" iconStart="3:130"
textLabel="Sign in" label="Sign in"
variantVariant="Neutral" variant="Neutral"
variantState="Default" state="Default" ... />
On the SDS page (Simple Design System), 209 prop attributes were flat-out duplicated. Add in the swap IDs and slot placeholders and that's 6,374 characters β 46.9% of the code β doing nothing but repeating itself.
A small post-processing script strips it clean, and a test confirms every value and tag structure survives untouched:
<NavigationPill label="Products" state="Active" />
That's 13,581 characters of code trimmed down to 7,207 β with zero information lost, and the output ends up looking a lot more like something a human developer would actually write.
Stripping duplication is a good habit. But the more aggressive move β cutting content, not just noise β is where things get genuinely risky. β
A custom extractor can build a compact spec: list each component's props exactly once, plus all the text content, nothing more. On the SDS page (Simple Design System) that came out to 8,690 characters β 48% of the full response, with nothing missing.
Here's the trap: the version most tutorials describe stops descending the moment it hits a component instance, on the theory that "the component already owns its internals, why re-read them." That produced a tiny 374-character output. Looks like a 98% savings β except it had silently thrown away the navigation labels, every card's title and body text, and all 24 footer links.
On a real design system, the actual content of a page lives inside nested instances and their properties β not just at the top level. The rule that actually works: skip a component's own internal layer structure, but keep descending into its nested instances and text.
Every finding so far is about a single screen. A real project is dozens of screens β and at that scale, the per-screen savings above stop being the story entirely. β
A single screen is a nice benchmark. A real project is dozens of screens β and at that scale, three costs take over completely.
Every tool result you fetch stays in the conversation and gets re-sent to the model on every future turn. With prompt caching active, writing something to cache costs 1.25Γ the normal input price, and each subsequent read costs just 0.1Γ. Do the math: a result that sticks around for 25 more turns costs about 3.75Γ its own size β and roughly 26Γ its size without caching at all. Large responses fetched early in a long session are the single most expensive habit you can form.
Every screen you fetch in one session just stacks on top of the last. Modeled at ~3.3 characters per token:
| Screens fetched in one session | Plain frames | Code Connect | Compact spec |
|---|---|---|---|
| 5 | ~45k tokens | ~27k | ~13k |
| 12 | ~109k | ~65k | ~32k |
| 25 | ~227k | ~136k | ~66k |
This isn't just a cost problem β it's an accuracy problem. By the twelfth screen, the agent is writing new code while eleven other screens' worth of markup is still sitting in its context, quietly competing for attention.
Figma counts every MCP read call against a monthly or daily limit. Current published limits: 20 calls/month on Starter, 200/day and 10/minute on Professional Dev or Full seats, rising to 600/day on Enterprise. Here's the trap that catches people off guard: limits follow whichever plan owns the file β not your own seat. A file parked in Drafts, or owned by a Starter-tier team, gets Starter-tier limits, even if you personally have a Professional seat. (I hit the Starter limit myself, partway through this very analysis.)
For a modeled 50-screen project:
| Flow | Read calls | Pro Dev seat (200/day) | Starter (20/month) |
|---|---|---|---|
| One context call + one screenshot per screen | ~101 | half a day | 5 months |
| Structure-first, component-based pages | ~401 | 2 days | 20 months |
| Structure-first, plain-frame pages | ~851 | 4.3 days | 42 months |
Read that last row again. The fetch strategy alone decides whether a project takes half a day or spills across a week β or, in the worst case, becomes mathematically impossible on a Starter plan.
On the SDS page (Simple Design System), the Header and Footer alone accounted for 34.6% of the generated code and 40% of the compact spec. On a real site, those same two components appear on every single screen. Build them once, then fetch only the page body on every subsequent screen β or tell your extractor to simply skip components that already exist in your codebase. Across a 20-screen project, that one change removes roughly a third of all remaining fetches.
Here's the part that's almost too good to be true: most of these outcomes are predictable before you fetch anything at all, just by looking at how the file is structured. A readiness audit scores each frame on four signals β how many component instances it uses, how much of it uses auto-layout, whether its fills use design tokens or raw colors, and how its root layout is set up.
Scores for the six frames tested here:
| Frame | Readiness score | Verdict |
|---|---|---|
| SDS About (Simple Design System) | ~85 | Ready to go |
| Landing, Shop, About, Article, Product | ~15β21 | Fix the file first |
The best part: this audit runs on Figma's REST API, which doesn't touch your MCP quota at all. It checks every frame in one request, and it hands design a clear to-do list before engineering even starts. It is always cheaper to fix a Figma file up front than to throw away and rewrite code that was generated from a broken one.
Phase 0 β Audit everything. One REST call, zero MCP quota spent. Sort every screen into ready, needs cleanup, or fix first β and hand the fix-first list straight to design.
Phase 1 β Build the foundations, once.
Phase 2 β One subagent per screen.
Phase 3 β Verify once per screen against a screenshot big enough to actually read. Only re-fetch the specific sections that fail the visual comparison.
Rules that apply across every phase:
What's worth measuring next: the same comparison on a production design system with deeper component nesting, and a direct measure of output quality β specifically, counting how many components get reused versus recreated from scratch across a batch of agent-built screens. That number is the one that ultimately decides whether any of these savings actually matter in practice.
Everyone optimizes for the token bill, because it's the cost you can see. On the files measured here, the real costs were hiding in three other places entirely:
Fix the file first. Fetch each screen exactly once. Give every screen a clean, fresh context window.
If you've hit Figma's rate limit mid-project, I'd like to know your plan tier and screen count β drop it in the comments.
I write about AI-assisted UI and full-stack development β the practical, measured side of building with AI tools rather than just the hype. More of this series is coming as I move from UI-focused work into full-stack.