'Avoid the most common Figma API integration mistakes and anti-patterns.
Scanned 9/2/2026
Install to Claude Code
npx -y skills add jeremylongshore/tons-of-skills-marketplace --skill figma-known-pitfalls --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Figma Known Pitfalls?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/jeremylongshore-figma-known-pitfalls-tons-of-skills-marketplace)More formats (shields.io, HTML) on the badges page.
---
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.
```typescript
// BAD -- downloads entire file tree
const file = await figmaFetch(`/v1/files/${fileKey}`);
// GOOD -- only get metadata and page names
const file = await figmaFetch(`/v1/files/${fileKey}?depth=1`);
// GOOD -- fetch only the nodes you need
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`.
```typescript
// BAD -- no rate limit handling
for (const id of nodeIds) {
await figmaFetch(`/v1/files/${fileKey}/nodes?ids=${id}`); // 429!
}
// GOOD -- batch IDs and honor Retry-After
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.
```typescript
// BAD -- storing image URLs in database permanently
await db.save({ iconUrl: imageUrl }); // Will break in 30 days
// GOOD -- re-export when needed, or cache with short TTL
const imageCache = new LRUCache({ max: 1000, ttl: 24 * 60 * 60 * 1000 }); // 24h
```
### Pitfall 4: Hardcoded PATs
**Problem:** Personal access tokens committed to source code.
```typescript
// BAD -- token in source code (visible forever in git history)
const token = 'figd_actual_token_value_here';
// GOOD -- environment variable
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.
```typescript
// BAD -- using Figma values directly as RGB
const { r, g, b } = node.fills[0].color;
return `rgb(${r}, ${g}, ${b})`; // rgb(0.8, 0.2, 0.4) -- invalid!
// GOOD -- convert to 0-255 range
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).
```typescript
// BAD -- assumes all nodes render successfully
const images = data.images;
for (const [id, url] of Object.entries(images)) {
const img = await fetch(url); // TypeError: Cannot construct URL from null
}
// GOOD -- filter out null entries
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.
```typescript
// BAD -- 2,880 API calls per file per day
setInterval(async () => {
const file = await figmaFetch(`/v1/files/${fileKey}`);
if (file.version !== lastVersion) await sync();
}, 30_000);
// GOOD -- webhook notifies you only when file changes
// POST /v2/webhooks with event_type: "FILE_UPDATE"
// Result: ~10-50 calls/day instead of 2,880
```
### Pitfall 9: SVG Export with Scale Parameter
**Problem:** Figma ignores the `scale` parameter for SVG exports. SVGs always export at 1x.
```typescript
// BAD -- scale has no effect on SVG
await figmaFetch(`/v1/images/${key}?ids=${id}&format=svg&scale=2`);
// GOOD -- SVG is vector; scale is meaningless. Use scale for PNG/JPG only.
await figmaFetch(`/v1/images/${key}?ids=${id}&format=svg`); // SVG: always 1x
await figmaFetch(`/v1/images/${key}?ids=${id}&format=png&scale=2`); // PNG: 2x
```
### Pitfall 10: Webhook Without Passcode Verification
**Problem:** Anyone can POST to your webhook endpoint if you don't verify the passcode.
```typescript
// BAD -- trusts any incoming request
app.post('/webhooks/figma', (req, res) => {
processEvent(req.body); // Attacker can send fake events
res.sendStatus(200);
});
// GOOD -- verify passcode with timing-safe comparison
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:
```bash
# Pitfall 4: hardcoded PATs anywhere in the repo
/usr/bin/grep -rn "figd_" --include='*.*' . | /usr/bin/grep -v node_modules
# Pitfall 1: full-tree fetches (no depth/nodes constraint)
/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:
```typescript
// Before: {"r":0.31,"g":0.27,"b":0.9} rendered as rgb(0,0,1)
const css = `rgb(${fill.color.r}, ${fill.color.g}, ${fill.color.b})`;
// After
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
- [Figma REST API](https://developers.figma.com/docs/rest-api/)
- [Figma Rate Limits](https://developers.figma.com/docs/rest-api/rate-limits/)
- [Figma API Scopes](https://developers.figma.com/docs/rest-api/scopes/)
- [Figma Webhooks V2](https://developers.figma.com/docs/rest-api/webhooks/)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!