| name | figma-known-pitfalls |
| description | Avoid the most common Figma API integration mistakes and anti-patterns.
Use when reviewing Figma code, onboarding new developers,
or auditing an existing Figma integration.
Trigger with phrases like "figma mistakes", "figma anti-patterns",
"figma pitfalls", "figma code review", "figma what not to do".
|
| allowed-tools | Read, Grep |
| version | 1.6.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
| tags | ["saas","figma"] |
| compatibility | Designed for Claude Code |
Figma Known Pitfalls
Overview
The ten most common mistakes when integrating with the Figma REST API and Plugin API, with correct alternatives for each.
Prerequisites
- Working Figma integration to audit
- Access to codebase
Instructions
Pitfall 1: Fetching Full File Trees
Problem: GET /v1/files/:key without depth returns the entire document tree. Large files can be 10-100 MB of JSON.
const file = await figmaFetch(`/v1/files/${fileKey}`);
const file = await figmaFetch(`/v1/files/${fileKey}?depth=1`);
const nodes = await figmaFetch(`/v1/files/${fileKey}/nodes?ids=${ids}`);
Pitfall 2: Ignoring Rate Limit Headers
Problem: Blasting requests and crashing on 429 without reading Retry-After.
for (const id of nodeIds) {
await figmaFetch(`/v1/files/${fileKey}/nodes?ids=${id}`);
}
const ids = nodeIds.join(',');
const res = await fetch(`https://api.figma.com/v1/files/${fileKey}/nodes?ids=${ids}`, {
headers: { 'X-Figma-Token': token },
});
if (res.status === 429) {
const wait = parseInt(res.headers.get('Retry-After') || '60');
await new Promise(r => setTimeout(r, wait * 1000));
}
Pitfall 3: Caching Image Export URLs Too Long
Problem: Figma image URLs expire after 30 days. Storing them permanently breaks.
await db.save({ iconUrl: imageUrl });
const imageCache = new LRUCache({ max: 1000, ttl: 24 * 60 * 60 * 1000 });
Pitfall 4: Hardcoded PATs
Problem: Personal access tokens committed to source code.
const token = 'figd_actual_token_value_here';
const token = process.env.FIGMA_PAT!;
if (!token) throw new Error('FIGMA_PAT not set');
Pitfall 5: Using Deprecated files:read Scope
Problem: The files:read scope is deprecated. New tokens should use granular scopes.
BAD: files:read (deprecated, will be removed)
GOOD: file_content:read, file_comments:read, file_versions:read (specific)
Pitfall 6: Forgetting Color Format Conversion
Problem: Figma returns colors as 0-1 floats, not 0-255 integers.
const { r, g, b } = node.fills[0].color;
return `rgb(${r}, ${g}, ${b})`;
return `rgb(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)})`;
Pitfall 7: Not Handling null Image Renders
Problem: The images endpoint returns null for nodes that cannot be rendered (invisible, deleted, empty).
const images = data.images;
for (const [id, url] of Object.entries(images)) {
const img = await fetch(url);
}
for (const [id, url] of Object.entries(images)) {
if (!url) {
console.warn(`Node ${id} could not be rendered (null)`);
continue;
}
const img = await fetch(url);
}
Pitfall 8: Polling Instead of Webhooks
Problem: Polling GET /v1/files/:key every 30 seconds wastes rate limit quota.
setInterval(async () => {
const file = await figmaFetch(`/v1/files/${fileKey}`);
if (file.version !== lastVersion) await sync();
}, 30_000);
Pitfall 9: SVG Export with Scale Parameter
Problem: Figma ignores the scale parameter for SVG exports. SVGs always export at 1x.
await figmaFetch(`/v1/images/${key}?ids=${id}&format=svg&scale=2`);
await figmaFetch(`/v1/images/${key}?ids=${id}&format=svg`);
await figmaFetch(`/v1/images/${key}?ids=${id}&format=png&scale=2`);
Pitfall 10: Webhook Without Passcode Verification
Problem: Anyone can POST to your webhook endpoint if you don't verify the passcode.
app.post('/webhooks/figma', (req, res) => {
processEvent(req.body);
res.sendStatus(200);
});
app.post('/webhooks/figma', (req, res) => {
const received = req.body.passcode || '';
const expected = process.env.FIGMA_WEBHOOK_PASSCODE!;
if (received.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))) {
return res.status(401).json({ error: 'Invalid passcode' });
}
res.status(200).json({ received: true });
processEvent(req.body);
});
Output
- A pitfall-by-pitfall review of your integration, each with detection command and fix
- The Quick Reference table (below in this skill) mapping all 10 pitfalls to detection signals
- Concrete code corrections:
?depth=1//nodes?ids= fetches, Retry-After handling, env-var PATs, file_content:read scope, x255 color conversion, null-render filtering, webhook subscriptions with passcode verification
Error Handling
| Symptom | Pitfall | Fix |
|---|
| Responses > 1 MB, slow syncs, memory spikes | #1 full-tree fetches | ?depth=1 or /nodes?ids= (references/pitfall-1-fetching-full-file-trees.md) |
| Bursts of 429s under load | #2 ignoring rate-limit headers | Honor Retry-After, batch requests (references/pitfall-2-ignoring-rate-limit-headers.md) |
| Images break ~30 days after export | #3 cached export URLs | Re-export on demand or cache with short TTL |
figd_... in source control | #4 hardcoded PATs | Move to process.env.FIGMA_PAT, rotate the leaked token immediately |
| Colors render wrong in generated CSS | #6 color format | Multiply Figma's 0-1 floats by 255 |
TypeError reading image URL | #7 null renders | Filter null entries from /v1/images responses |
| Webhook events processed from unknown senders | #10 no passcode check | Verify passcode on every delivery (references/pitfall-10-webhook-without-passcode-verification.md) |
Quick Reference
| # | Pitfall | Detection | Fix |
|---|
| 1 | Full file fetch | Response > 1MB | Use depth=1 or /nodes |
| 2 | No rate limit handling | 429 errors | Read Retry-After, batch requests |
| 3 | Stale image URLs | Broken images after 30 days | Re-export or short TTL cache |
| 4 | Hardcoded PAT | grep -r figd_ in source | Use process.env.FIGMA_PAT |
| 5 | Deprecated scope | files:read in token config | Use file_content:read |
| 6 | Wrong color format | Colors look wrong | Multiply by 255 |
| 7 | Null image render | TypeError on null URL | Filter null entries |
| 8 | Polling loop | High API call volume | Use Webhooks V2 |
| 9 | SVG with scale | Scale parameter ignored | SVG is always 1x |
| 10 | No webhook verification | Security vulnerability | Verify passcode |
Examples
Audit an existing integration for the two highest-impact pitfalls in one pass:
/usr/bin/grep -rn "figd_" --include='*.*' . | /usr/bin/grep -v node_modules
/usr/bin/grep -rn "api.figma.com/v1/files/" --include='*.{ts,js}' . \
| /usr/bin/grep -v -e 'depth=' -e '/nodes'
Fix a color-conversion bug (Pitfall 6) — before/after:
const css = `rgb(${fill.color.r}, ${fill.color.g}, ${fill.color.b})`;
const to255 = (v: number) => Math.round(v * 255);
const css = `rgb(${to255(fill.color.r)}, ${to255(fill.color.g)}, ${to255(fill.color.b)})`;
Every pitfall has a dedicated deep-dive under references/ (e.g. references/pitfall-8-polling-instead-of-webhooks.md).
Resources