agentsclimarketplace

Figma known pitfalls

Skill jeremylongshore/claude-code-plugins-plus-skills/skills/.curated/figma-known-pitfalls

425 plugins, 2,810 skills, 200 agents for Claude Code. Open-source marketplace at tonsofskills.com with the ccpi CLI package manager.

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill figma-known-pitfalls

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

'Avoid the most common Figma API integration mistakes and anti-patterns.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

9.0 KB, as published. Nobody here has run it

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.

// 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.

// 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.

// 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.

// 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.

// 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).

// 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.

// 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.

// 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.

// 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

SymptomPitfallFix
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 headersHonor Retry-After, batch requests (references/pitfall-2-ignoring-rate-limit-headers.md)
Images break ~30 days after export#3 cached export URLsRe-export on demand or cache with short TTL
figd_... in source control#4 hardcoded PATsMove to process.env.FIGMA_PAT, rotate the leaked token immediately
Colors render wrong in generated CSS#6 color formatMultiply Figma's 0-1 floats by 255
TypeError reading image URL#7 null rendersFilter null entries from /v1/images responses
Webhook events processed from unknown senders#10 no passcode checkVerify passcode on every delivery (references/pitfall-10-webhook-without-passcode-verification.md)

Quick Reference

#PitfallDetectionFix
1Full file fetchResponse > 1MBUse depth=1 or /nodes
2No rate limit handling429 errorsRead Retry-After, batch requests
3Stale image URLsBroken images after 30 daysRe-export or short TTL cache
4Hardcoded PATgrep -r figd_ in sourceUse process.env.FIGMA_PAT
5Deprecated scopefiles:read in token configUse file_content:read
6Wrong color formatColors look wrongMultiply by 255
7Null image renderTypeError on null URLFilter null entries
8Polling loopHigh API call volumeUse Webhooks V2
9SVG with scaleScale parameter ignoredSVG is always 1x
10No webhook verificationSecurity vulnerabilityVerify passcode

Examples

Audit an existing integration for the two highest-impact pitfalls in one pass:

# 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:

// 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

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.