figma-pack
Claude Code skill pack for Figma (30 skills)
Installation
Open Claude Code and run this command:
/plugin install figma-pack@claude-code-plugins-plus
Use --global to install for all projects, or --project for current project only.
What It Does
> 30 production-grade Claude Code skills for the Figma REST API and Plugin API
Build design-to-code pipelines, extract design tokens, export assets, handle webhooks, and manage Enterprise features -- all with real Figma API endpoints, actual response shapes, and working TypeScript code.
Skills (30)
'Deep debugging for Figma API issues: network analysis, response inspection,.
Figma Advanced Troubleshooting
Overview
Deep debugging techniques for complex Figma REST API issues that resist standard error handling: intermittent failures, unexpected response shapes, rate limit edge cases, and large file timeouts.
Prerequisites
- Access to application logs
curlwith verbose mode for network inspection- Figma API credentials for testing
Instructions
Step 1: Verbose Request Inspection
# Full HTTP request/response trace for a Figma API call
curl -v -H "X-Figma-Token: ${FIGMA_PAT}" \
"https://api.figma.com/v1/files/${FIGMA_FILE_KEY}?depth=1" 2>&1 \
| tee figma-debug-trace.txt
# Extract key diagnostic info:
# - TLS version and cipher
# - Response status and headers
# - Timing breakdown
curl -w "
DNS: %{time_namelookup}s
Connect: %{time_connect}s
TLS: %{time_appconnect}s
TTFB: %{time_starttransfer}s
Total: %{time_total}s
Size: %{size_download} bytes
Status: %{http_code}
" -s -o /dev/null \
-H "X-Figma-Token: ${FIGMA_PAT}" \
"https://api.figma.com/v1/files/${FIGMA_FILE_KEY}?depth=1"
Step 2: Response Shape Validation
// Figma API responses can be unexpectedly shaped when:
// - File is empty or newly created
// - Nodes have been deleted between requests
// - Plugin data is corrupted
function validateFileResponse(data: any): string[] {
const issues: string[] = [];
if (!data.document) issues.push('Missing document root');
if (!data.document?.children?.length) issues.push('Document has no pages');
if (typeof data.name !== 'string') issues.push('Missing file name');
if (!data.version) issues.push('Missing version field');
// Check for null nodes (deleted between list and fetch)
if (data.nodes) {
for (const [id, node] of Object.entries(data.nodes)) {
if (node === null) issues.push(`Null node: ${id} (deleted or invisible)`);
}
}
// Check images response for null renders
if (data.images) {
for (const [id, url] of Object.entries(data.images)) {
if (url === null) issues.push(`Image render failed for node: ${id}`);
}
}
return issues;
}
Step 3: Rate Limit Edge Cases
// Problem: Figma rate limits are per-user, per-minute, but the exact
// limit is not published and varies by plan tier and seat type.
// Diagnostic: measure your actual limit by counting successful requests
async function measureRateLimit(token: string): Promise<{
requestsMade: number;
firstRateLimitAt: number | null;
retryAfter: number | null;
}> {
let count = 0;
let rateLimitAt: number | null = null;
let retryAfter: number | null = null;
// Make requests until rate limited (use a read-only endpoint)
while (count < 2'Choose between Figma integration architectures: CLI script, webhook.
Figma Architecture Variants
Overview
Three proven architecture patterns for Figma integrations, based on the two primary Figma APIs: the REST API (external tools) and the Plugin API (in-editor experiences).
Prerequisites
- Clear use case requirements
- Understanding of Figma REST API vs Plugin API differences
Instructions
Step 1: Choose Your Architecture
| Architecture | API Used | Best For | Hosting |
|---|---|---|---|
| CLI/Script | REST API | Design token sync, asset export | None (runs locally or in CI) |
| Webhook Service | REST API | Real-time automation, Slack bots | Server/serverless |
| Figma Plugin | Plugin API | In-editor tools, design linting | Runs in Figma desktop app |
Variant A: CLI Script (Simplest)
Use case: Extract design tokens, export icons, sync to code
Developer runs script
│
▼
┌─────────────┐
│ CLI Script │ (Node.js)
│ - extract.ts │
└──────┬───────┘
│ GET /v1/files/:key
│ GET /v1/images/:key
▼
┌─────────────┐
│ Figma REST │
│ API │
└──────┬──────┘
│
▼
┌─────────────┐
│ Output │
│ - tokens.css│
│ - icons/ │
└─────────────┘
{
"scripts": {
"figma:tokens": "tsx scripts/extract-tokens.ts",
"figma:icons": "tsx scripts/export-icons.ts",
"figma:sync": "npm run figma:tokens && npm run figma:icons"
}
}
Pros: Zero infrastructure, runs in CI, easy to debug
Cons: Not real-time, manual trigger, no webhook support
Variant B: Webhook Service (Event-Driven)
Use case: Auto-sync on file save, Slack notifications, build triggers
┌─────────────┐
│ Figma Cloud │
│ FILE_UPDATE │──── Webhook V2 ────┐
│ FILE_COMMENT │ │
└──────────────┘ │
▼
┌──────────────┐
│ Your Service │
│ (Vercel/Fly) │
├──────────────┤
│ /webhooks │ ← Verify passcode
│ /health │
│ /api/tokens │
└──────┬───────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Token │ │ Slack │ │ CI │
│ 'Automate Figma design token sync and asset export in CI/CD pipelines.
Figma CI Integration
Overview
Automate Figma API workflows in CI/CD: sync design tokens on schedule, export assets on PR, and validate design system consistency.
Prerequisites
- GitHub repository with Actions enabled
FIGMA_PATstored as GitHub secret- Design token extraction script (from
figma-core-workflow-a)
Instructions
Step 1: Scheduled Token Sync Workflow
# .github/workflows/figma-token-sync.yml
name: Sync Figma Design Tokens
on:
schedule:
- cron: '0 9 * * 1-5' # Weekdays at 9am UTC
workflow_dispatch: # Manual trigger
jobs:
sync-tokens:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Extract tokens from Figma
env:
FIGMA_PAT: ${{ secrets.FIGMA_PAT }}
FIGMA_FILE_KEY: ${{ vars.FIGMA_FILE_KEY }}
run: node scripts/extract-figma-tokens.mjs
- name: Check for changes
id: diff
run: |
git diff --quiet src/styles/tokens.css || echo "changed=true" >> $GITHUB_OUTPUT
- name: Create PR with token updates
if: steps.diff.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
BRANCH="figma/token-sync-$(date +%Y%m%d)"
git checkout -b "$BRANCH"
git add src/styles/tokens.css
git commit -m "chore: sync design tokens from Figma"
git push origin "$BRANCH"
gh pr create \
--title "Sync design tokens from Figma" \
--body "Automated token sync from Figma file. Review the CSS changes." \
--label "design-tokens,automated"
Step 2: Asset Export on PR
# .github/workflows/figma-asset-export.yml
name: Export Figma Assets
on:
pull_request:
paths:
- 'figma-config.json' # Trigger when asset config changes
jobs:
export-assets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- name: Export icons from Figma
env:
FIGMA_PAT: ${{ secrets.FIGMA_PAT }}
FIGMA_FILE_KEY: ${{ vars.FIGMA_ICON_FILE_KEY }}
FIGMA_ICON_FRAME: ${{ vars.FIGMA_ICON_FRAME_ID }}
run: node scripts/export-figma-icons.mjs
- name: Commit exported assets
run: |
git add assets/icons/
if ! git diff --cached --quiet; then
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
'Diagnose and fix common Figma REST API and Plugin API errors.
Figma Common Errors
Overview
Quick reference for the most common Figma REST API and Plugin API errors, with exact error messages and working solutions.
Prerequisites
- Figma API credentials configured
- Access to your application logs or browser console
Instructions
Step 1: Identify the Error Category
REST API HTTP Errors
| Status | Error | Cause | Solution |
|---|---|---|---|
| 400 | Bad Request | Malformed request, invalid node IDs | Verify node ID format (pageId:nodeId, e.g., 0:1) |
| 403 | Forbidden | Invalid token, wrong scopes, no file access | Regenerate PAT with correct scopes; verify file sharing |
| 404 | Not Found | Wrong file key, deleted file, wrong endpoint | Check file key from URL; verify file exists |
| 429 | Rate Limited | Too many requests | Read Retry-After header; implement backoff |
| 500 | Internal Server Error | Figma server issue | Retry with exponential backoff; check status.figma.com |
Step 2: Diagnose Specific Errors
403 Forbidden -- Token Issues
# Test your token
curl -s -o /dev/null -w "%{http_code}" \
-H "X-Figma-Token: ${FIGMA_PAT}" \
https://api.figma.com/v1/me
# 200 = token valid, 403 = invalid/expired
# Check what scopes your request needs
# file_content:read -> GET /v1/files/:key
# file_comments:read -> GET /v1/files/:key/comments
# file_variables:read -> GET /v1/files/:key/variables/local
# webhooks:write -> POST /v2/webhooks
Common 403 causes:
- PAT expired (90-day maximum lifetime)
- Token missing required scope (e.g., using
file_content:readbut calling comments endpoint) - File not shared with the token owner
- OAuth token not refreshed after expiry
429 Rate Limited
// Figma returns these headers on 429:
// Retry-After: <seconds> -- wait this long before retrying
// X-Figma-Rate-Limit-Type: <type> -- "low" or "high" tier
// X-Figma-Plan-Tier: <plan> -- your plan level
async function handleRateLimit(response: Response) {
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
const limitType = response.headers.get('X-Figma-Rate-Limit-Type');
console.warn(`Rate limited (${limitType}). Retrying in ${retryAfter}s`);
await new Promise(r => setTimeout(r, retryAfter * 1000));
return true; // signal to retry
}
return false;
}
404 Not Found
<
'Extract design tokens, colors, typography, and spacing from Figma files.
Figma Core Workflow A -- Design Token Extraction
Overview
The primary workflow for Figma API integrations: extracting design tokens (colors, typography, spacing) from a Figma file and converting them to CSS custom properties, JSON tokens, or Tailwind config.
Prerequisites
- Completed
figma-install-authsetup - A Figma file with published styles or variables
FIGMAPATandFIGMAFILE_KEYenv vars set
Instructions
Step 1: Fetch Styles from a File
import { FigmaClient } from './figma-client';
const client = new FigmaClient(process.env.FIGMA_PAT!);
const fileKey = process.env.FIGMA_FILE_KEY!;
// GET /v1/files/:key -- returns styles map in response
const file = await client.getFile(fileKey);
// file.styles is a map: nodeId -> { key, name, style_type, description }
// style_type: "FILL" | "TEXT" | "EFFECT" | "GRID"
const colorStyles = Object.entries(file.styles)
.filter(([, s]) => s.style_type === 'FILL')
.map(([nodeId, s]) => ({ nodeId, name: s.name }));
const textStyles = Object.entries(file.styles)
.filter(([, s]) => s.style_type === 'TEXT')
.map(([nodeId, s]) => ({ nodeId, name: s.name }));
console.log(`Found ${colorStyles.length} color styles, ${textStyles.length} text styles`);
Step 2: Resolve Style Values from Nodes
// Fetch the actual nodes to get fill colors and text properties
const styleNodeIds = colorStyles.map(s => s.nodeId);
const nodesResponse = await client.getFileNodes(fileKey, styleNodeIds);
interface DesignToken {
name: string;
type: 'color' | 'typography' | 'spacing';
value: string;
}
const tokens: DesignToken[] = [];
for (const [nodeId, nodeData] of Object.entries(nodesResponse.nodes)) {
const node = nodeData.document;
const styleName = colorStyles.find(s => s.nodeId === nodeId)?.name;
if (node.fills?.[0]?.type === 'SOLID' && node.fills[0].color) {
const { r, g, b, a } = node.fills[0].color;
// Figma colors are 0-1 floats; convert to 0-255
const hex = '#' + [r, g, b].map(v =>
Math.round(v * 255).toString(16).padStart(2, '0')
).join('');
tokens.push({
name: styleName ?? node.name,
type: 'color',
value: a !== undefined && a < 1
? `rgba(${Math.round(r*255)}, ${Math.round(g*255)}, ${Math.round(b*255)}, ${a.toFixed(2)})`
: hex,
});
}
}
Step 3: Extract Typography Tokens
// Fetch text style nodes
const textNodeIds = textStyles.map(s => s.nodeId);
const textNodes = await client.getFileNodes(fileKey, textNodeIds);
for (const [nodeId, nodeData] of Object.entries(textNodes.nodes)) {
const node = no'Export images, icons, and assets from Figma files via the REST API.
Figma Core Workflow B -- Asset Export
Overview
Export images, icons, and assets from Figma files using the REST API. Render specific nodes as PNG, SVG, JPG, or PDF. Build automated asset pipelines for icons, illustrations, and component previews.
Prerequisites
- Completed
figma-install-authsetup - Node IDs of the frames/components to export (from
figma-hello-world) FIGMAPATandFIGMAFILE_KEYenv vars set
Instructions
Step 1: Render Nodes as Images
const PAT = process.env.FIGMA_PAT!;
const FILE_KEY = process.env.FIGMA_FILE_KEY!;
// GET /v1/images/:file_key?ids=X,Y&format=png&scale=2
// Supported formats: png, svg, jpg, pdf
// Scale: 0.01 to 4 (SVG always exports at 1x)
// Max image size: 32 megapixels (larger images are auto-scaled down)
async function exportImages(
nodeIds: string[],
format: 'png' | 'svg' | 'jpg' | 'pdf' = 'png',
scale = 2
): Promise<Record<string, string | null>> {
const params = new URLSearchParams({
ids: nodeIds.join(','),
format,
scale: String(format === 'svg' ? 1 : scale), // SVG is always 1x
});
const res = await fetch(
`https://api.figma.com/v1/images/${FILE_KEY}?${params}`,
{ headers: { 'X-Figma-Token': PAT } }
);
if (!res.ok) throw new Error(`Image export failed: ${res.status}`);
const data = await res.json();
// data.images: { "nodeId": "https://..." | null }
// null means the node failed to render (invisible, 0% opacity, or invalid ID)
// URLs expire after 30 days
return data.images;
}
Step 2: Download Exported Images
import { writeFileSync, mkdirSync } from 'fs';
import { join } from 'path';
async function downloadAssets(
nodeIds: string[],
outputDir: string,
format: 'png' | 'svg' = 'svg'
) {
mkdirSync(outputDir, { recursive: true });
const imageUrls = await exportImages(nodeIds, format);
const results: { nodeId: string; path: string; success: boolean }[] = [];
for (const [nodeId, url] of Object.entries(imageUrls)) {
if (!url) {
console.warn(`Node ${nodeId}: render returned null (invisible or invalid)`);
results.push({ nodeId, path: '', success: false });
continue;
}
const res = await fetch(url);
const buffer = Buffer.from(await res.arrayBuffer());
const filename = `${nodeId.replace(':', '-')}.${format}`;
const filepath = join(outputDir, filename);
writeFileSync(filepath, buffer);
results.push({ nodeId, path: filepath, success: true });
}
return results;
}
Step 3: Export All Icons from a Frame
// Find all COMPONENT children in an "Ico'Optimize Figma API usage to minimize costs and stay within plan limits.
Figma Cost Tuning
Overview
Optimize Figma API usage costs. Figma's REST API rate limits are determined by plan tier and seat type. Reducing unnecessary requests keeps you within limits and avoids upgrading prematurely.
Prerequisites
- Working Figma integration with request logging
- Understanding of your current API call volume
- Access to Figma admin settings (for plan details)
Instructions
Step 1: Understand Plan-Based Rate Limits
Figma rate limits vary by plan tier and seat type:
| Plan | Seat Types | Rate Limit Tier | Variables API |
|---|---|---|---|
| Starter (Free) | Free | Lowest | No |
| Professional | Full, Viewer | Standard | No |
| Organization | Full, Collab, Viewer | Higher | No |
| Enterprise | Full, Collab, Viewer | Highest | Yes |
Key facts:
- Rate limits are per-user, per-minute
- View and Collab seats have lower limits than Full seats
- The Variables API (
/v1/files/:key/variables/*) requires Enterprise - Endpoint tiers (1/2/3) have different quotas within each plan
Step 2: Track API Usage
// Instrument all Figma API calls to track volume
class FigmaUsageTracker {
private calls: Array<{ endpoint: string; timestamp: number; cached: boolean }> = [];
record(endpoint: string, cached: boolean) {
this.calls.push({ endpoint, timestamp: Date.now(), cached });
}
getReport(windowMs = 24 * 60 * 60 * 1000) {
const cutoff = Date.now() - windowMs;
const recent = this.calls.filter(c => c.timestamp > cutoff);
// Group by endpoint
const byEndpoint = new Map<string, { total: number; cached: number }>();
for (const call of recent) {
const key = call.endpoint.replace(/[a-zA-Z0-9]{20,}/, ':key');
const entry = byEndpoint.get(key) || { total: 0, cached: 0 };
entry.total++;
if (call.cached) entry.cached++;
byEndpoint.set(key, entry);
}
return {
totalCalls: recent.length,
cachedCalls: recent.filter(c => c.cached).length,
cacheHitRate: recent.length > 0
? (recent.filter(c => c.cached).length / recent.length * 100).toFixed(1) + '%'
: '0%',
byEndpoint: Object.fromEntries(byEndpoint),
};
}
}
const tracker = new FigmaUsageTracker();
Step 3: Reduce API Calls
// 1. Use depth parameter to avoid fetching full file trees
// Saves bandwidth and processing time
const fileMeta = await figmaFetch(`/v1/files/${key}?depth=1`);
// 2. Batch node IDs into single requests
// Instead of 50 individual /nodes calls, make 1 call with 50 IDs
const ids'Handle Figma API data correctly: comments, versions, user data, and.
Figma Data Handling
Overview
Work with Figma's data APIs: comments, version history, and user information. Handle sensitive data correctly with redaction and privacy compliance.
Prerequisites
FIGMAPATwith appropriate scopes (filecomments:read/write,file_versions:read)- Understanding of GDPR/CCPA basics
Instructions
Step 1: Comments API
const PAT = process.env.FIGMA_PAT!;
const FILE_KEY = process.env.FIGMA_FILE_KEY!;
// GET /v1/files/:key/comments -- requires file_comments:read scope
async function getComments(fileKey: string) {
const res = await fetch(
`https://api.figma.com/v1/files/${fileKey}/comments`,
{ headers: { 'X-Figma-Token': PAT } }
);
const data = await res.json();
// data.comments is an array of:
// { id, message, file_key, parent_id, user, client_meta, resolved_at, created_at, order_id }
return data.comments;
}
// GET with as_md=true to get rich-text comments as markdown
async function getCommentsAsMarkdown(fileKey: string) {
const res = await fetch(
`https://api.figma.com/v1/files/${fileKey}/comments?as_md=true`,
{ headers: { 'X-Figma-Token': PAT } }
);
return (await res.json()).comments;
}
// POST /v1/files/:key/comments -- requires file_comments:write scope
async function postComment(fileKey: string, message: string, nodeId?: string) {
const body: any = { message };
if (nodeId) {
body.client_meta = { node_id: nodeId };
}
const res = await fetch(
`https://api.figma.com/v1/files/${fileKey}/comments`,
{
method: 'POST',
headers: {
'X-Figma-Token': PAT,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}
);
return res.json();
}
// POST reactions to a comment -- requires file_comments:write
async function reactToComment(fileKey: string, commentId: string, emoji: string) {
return fetch(
`https://api.figma.com/v1/files/${fileKey}/comments/${commentId}/reactions`,
{
method: 'POST',
headers: {
'X-Figma-Token': PAT,
'Content-Type': 'application/json',
},
body: JSON.stringify({ emoji }),
}
).then(r => r.json());
}
Step 2: Version History API
// GET /v1/files/:key/versions -- requires file_versions:read scope
async function getVersionHistory(fileKey: string) {
const res = await fetch(
`https://api.figma.com/v1/files/${fileKey}/versions`,
{ headers: { 'X-Figma-Token': PAT } }
);
const data = await res.json();
// data.versions: array of { id, created_at, label, description, user }
// Ordered by created_at (most recent first)
return data.versions;
}
// Paginate through all versions
async function getAllVersions(fileKey: string) {
const v'Collect Figma API diagnostic evidence for support tickets and troubleshooting.
Figma Debug Bundle
Overview
Collect all diagnostic data needed to troubleshoot Figma REST API issues or submit a support request. Outputs a redacted archive with connectivity tests, token validation, rate limit status, and API response samples.
Prerequisites
FIGMA_PATenvironment variable setcurlandjqavailable- A Figma file key to test against
Instructions
Step 1: Create Debug Bundle Script
#!/bin/bash
# figma-debug-bundle.sh
set -euo pipefail
BUNDLE_DIR="figma-debug-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BUNDLE_DIR"
echo "=== Figma Debug Bundle ===" | tee "$BUNDLE_DIR/summary.txt"
echo "Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$BUNDLE_DIR/summary.txt"
echo "---" >> "$BUNDLE_DIR/summary.txt"
# 1. Environment info
echo "--- Environment ---" >> "$BUNDLE_DIR/summary.txt"
echo "Node: $(node --version 2>/dev/null || echo 'not installed')" >> "$BUNDLE_DIR/summary.txt"
echo "npm: $(npm --version 2>/dev/null || echo 'not installed')" >> "$BUNDLE_DIR/summary.txt"
echo "OS: $(uname -srm)" >> "$BUNDLE_DIR/summary.txt"
echo "PAT configured: ${FIGMA_PAT:+YES (${#FIGMA_PAT} chars)}" >> "$BUNDLE_DIR/summary.txt"
echo "File key: ${FIGMA_FILE_KEY:-NOT SET}" >> "$BUNDLE_DIR/summary.txt"
# 2. API connectivity test
echo "" >> "$BUNDLE_DIR/summary.txt"
echo "--- Connectivity ---" >> "$BUNDLE_DIR/summary.txt"
echo -n "GET /v1/me: " >> "$BUNDLE_DIR/summary.txt"
curl -s -o "$BUNDLE_DIR/me.json" -w "%{http_code} %{time_total}s" \
-H "X-Figma-Token: ${FIGMA_PAT}" \
https://api.figma.com/v1/me >> "$BUNDLE_DIR/summary.txt"
echo "" >> "$BUNDLE_DIR/summary.txt"
# 3. File access test (if key is set)
if [ -n "${FIGMA_FILE_KEY:-}" ]; then
echo -n "GET /v1/files: " >> "$BUNDLE_DIR/summary.txt"
curl -s -o "$BUNDLE_DIR/file-meta.json" -w "%{http_code} %{time_total}s" \
-H "X-Figma-Token: ${FIGMA_PAT}" \
"https://api.figma.com/v1/files/${FIGMA_FILE_KEY}?depth=1" >> "$BUNDLE_DIR/summary.txt"
echo "" >> "$BUNDLE_DIR/summary.txt"
fi
# 4. Rate limit check (capture response headers)
echo "" >> "$BUNDLE_DIR/summary.txt"
echo "--- Rate Limit Headers ---" >> "$BUNDLE_DIR/summary.txt"
curl -s -D "$BUNDLE_DIR/headers.txt" -o /dev/null \
-H "X-Figma-Token: ${FIGMA_PAT}" \
https://api.figma.com/v1/me
grep -iE'Deploy Figma-powered applications to Vercel, Cloud Run, and Fly.
Figma Deploy Integration
Overview
Deploy Figma webhook receivers and design API services to production platforms with proper secret management and health checks.
Prerequisites
- Figma PAT for production environment
- Platform CLI installed (vercel, fly, or gcloud)
- Application tested locally with Figma API
Instructions
Step 1: Vercel Deployment (Webhook Receiver)
# Store Figma secrets
vercel env add FIGMA_PAT production
vercel env add FIGMA_WEBHOOK_PASSCODE production
# Deploy
vercel --prod
// api/webhooks/figma.ts (Vercel serverless function)
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
export async function POST(req: NextRequest) {
const payload = await req.json();
// Verify passcode
const expected = process.env.FIGMA_WEBHOOK_PASSCODE!;
const received = payload.passcode || '';
const a = Buffer.from(received);
const b = Buffer.from(expected);
// timingSafeEqual throws on length mismatch — guard first
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return NextResponse.json({ error: 'Invalid passcode' }, { status: 401 });
}
// Process webhook event
switch (payload.event_type) {
case 'FILE_UPDATE':
console.log(`File updated: ${payload.file_name} (${payload.file_key})`);
// Trigger token re-sync, invalidate cache, etc.
break;
case 'FILE_COMMENT':
console.log(`New comment on ${payload.file_name}`);
break;
case 'LIBRARY_PUBLISH':
console.log(`Library published: ${payload.file_name}`);
break;
}
return NextResponse.json({ received: true });
}
export const config = { maxDuration: 10 };
Step 2: Google Cloud Run (Design Token API)
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY dist/ ./dist/
ENV PORT=8080
CMD ["node", "dist/server.js"]
PROJECT_ID="${GOOGLE_CLOUD_PROJECT}"
SERVICE="figma-token-api"
REGION="us-central1"
# Store PAT in Secret Manager
echo -n "${FIGMA_PAT}" | gcloud secrets create figma-pat --data-file=-
# Build and deploy
gcloud builds submit --tag gcr.io/$PROJECT_ID/$SERVICE
gcloud run deploy $SERVICE \
--image gcr.io/$PROJECT_ID/$SERVICE \
--region $REGION \
--platform managed \
--set-secrets="FIGMA_PAT=figma-pat:latest" \
--allow-unauthenticated \
--max-instances=5 \
--timeout=30s
Step 3: Fly.io (Persistent Webhook Service)
# fly.toml
app = "figma-webhook-service"
primary_region = "iad"
[env]
NODE_ENV = "production"
[http_service]
internal_port = 3000
force_https = true
auto_st'Configure Figma Enterprise features: OAuth 2.
Figma Enterprise RBAC
Overview
Figma Enterprise features accessible via the REST API: OAuth 2.0 for user-facing apps, team/project management, and the Variables API (Enterprise-only). This skill covers building OAuth integrations and managing organizational access.
Prerequisites
- Figma Enterprise or Organization plan
- OAuth app registered in Figma developer dashboard
- Understanding of OAuth 2.0 authorization code flow
Instructions
Step 1: OAuth 2.0 App Setup
// Figma OAuth 2.0 Authorization Code Flow
// 1. Build authorization URL
function getAuthUrl(state: string): string {
const params = new URLSearchParams({
client_id: process.env.FIGMA_CLIENT_ID!,
redirect_uri: process.env.FIGMA_REDIRECT_URI!,
scope: 'file_content:read,file_comments:write,file_variables:read',
state,
response_type: 'code',
});
return `https://www.figma.com/oauth?${params}`;
}
// 2. Exchange authorization code for tokens (within 30 seconds!)
async function exchangeCode(code: string): Promise<{
access_token: string;
refresh_token: string;
expires_in: number;
user_id: string;
}> {
const res = await fetch('https://api.figma.com/v1/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: process.env.FIGMA_CLIENT_ID!,
client_secret: process.env.FIGMA_CLIENT_SECRET!,
redirect_uri: process.env.FIGMA_REDIRECT_URI!,
code,
grant_type: 'authorization_code',
}),
});
if (!res.ok) {
const error = await res.text();
throw new Error(`Token exchange failed: ${res.status} ${error}`);
}
return res.json();
}
// 3. Refresh expired tokens
async function refreshAccessToken(refreshToken: string): Promise<{
access_token: string;
expires_in: number;
}> {
const res = await fetch('https://api.figma.com/v1/oauth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: process.env.FIGMA_CLIENT_ID!,
client_secret: process.env.FIGMA_CLIENT_SECRET!,
refresh_token: refreshToken,
}),
});
if (!res.ok) throw new Error(`Token refresh failed: ${res.status}`);
return res.json();
}
Step 2: OAuth Callback Handler
// Express callback handler
app.get('/auth/figma/callback', async (req, res) => {
const { code, state } = req.query;
// Verify state matches what we sent (CSRF protection)
if (state !== req.session.oauthState) {
return res.status(403).json({ error: 'Invalid state parameter' });
}
try {
// Exchange code within 30 seconds
const tokens = await exchangeCode(code as string);
// Get user info wi'Make your first Figma REST API call to fetch a file and inspect its.
Figma Hello World
Overview
Make your first Figma REST API call. Fetch a file's metadata and document tree, then inspect the node structure that represents every layer and object in a Figma design.
Prerequisites
- Completed
figma-install-authsetup - A Figma file key (from the URL:
figma.com/design/)/... FIGMA_PATenvironment variable set
Instructions
Step 1: Fetch a File
# Get the full document JSON for a file
curl -s -H "X-Figma-Token: ${FIGMA_PAT}" \
"https://api.figma.com/v1/files/${FIGMA_FILE_KEY}" | jq '{
name: .name,
lastModified: .lastModified,
version: .version,
pages: [.document.children[] | .name]
}'
Expected output:
{
"name": "My Design File",
"lastModified": "2025-03-15T10:30:00Z",
"version": "1234567890",
"pages": ["Page 1", "Components", "Tokens"]
}
Step 2: Understand the Node Tree
Every Figma file is a tree of typed nodes:
DOCUMENT (root)
├── CANVAS (page)
│ ├── FRAME (container / auto-layout)
│ │ ├── TEXT
│ │ ├── RECTANGLE
│ │ └── INSTANCE (component instance)
│ ├── GROUP
│ │ └── VECTOR
│ ├── COMPONENT (reusable master)
│ └── SECTION
Key node types: DOCUMENT, CANVAS, FRAME, GROUP, RECTANGLE, ELLIPSE, TEXT, VECTOR, COMPONENT, COMPONENTSET, INSTANCE, LINE, SECTION, BOOLEANOPERATION.
Step 3: TypeScript Hello World
// hello-figma.ts
const PAT = process.env.FIGMA_PAT!;
const FILE_KEY = process.env.FIGMA_FILE_KEY!;
interface FigmaNode {
id: string;
name: string;
type: string;
children?: FigmaNode[];
}
interface FigmaFileResponse {
name: string;
lastModified: string;
version: string;
document: FigmaNode;
components: Record<string, { key: string; name: string; description: string }>;
styles: Record<string, { key: string; name: string; style_type: string }>;
}
async function main() {
const res = await fetch(
`https://api.figma.com/v1/files/${FILE_KEY}`,
{ headers: { 'X-Figma-Token': PAT } }
);
if (!res.ok) {
throw new Error(`Figma API error: ${res.status} ${res.statusText}`);
}
const file: FigmaFileResponse = await res.json();
console.log(`File: ${file.name}`);
console.log(`Last modified: ${file.lastModified}`);
console.log(`Components: ${Object.keys(file.components).length}`);
console.log(`Styles: ${Object.keys(file.styles).length}`);
// Walk the first page and list top-level frames'Respond to Figma API outages, auth failures, and rate limit incidents.
Figma Incident Runbook
Overview
Rapid incident response procedures for Figma REST API integration failures. Covers triage, mitigation, and postmortem for the most common failure modes.
Prerequisites
- Access to application logs and metrics
- Figma PAT for health checks
- Communication channel (Slack, PagerDuty)
Instructions
Step 1: Quick Triage (First 5 Minutes)
#!/bin/bash
echo "=== Figma Incident Triage ==="
# 1. Is Figma itself down?
echo -n "Figma Status: "
curl -s https://www.figmastatus.com/api/v2/status.json 2>/dev/null \
| jq -r '.status.description // "Cannot reach status page"'
# 2. Is our token valid?
echo -n "Auth Check: "
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-H "X-Figma-Token: ${FIGMA_PAT}" \
https://api.figma.com/v1/me)
echo "$HTTP_CODE"
# 3. Can we read a known file?
echo -n "File Access: "
curl -s -H "X-Figma-Token: ${FIGMA_PAT}" \
"https://api.figma.com/v1/files/${FIGMA_FILE_KEY}?depth=1" \
| jq -r '.name // "FAILED"'
# 4. Are we rate limited?
echo "Rate Limit Headers:"
curl -s -D - -o /dev/null \
-H "X-Figma-Token: ${FIGMA_PAT}" \
https://api.figma.com/v1/me 2>/dev/null \
| grep -iE "(retry-after|rate-limit|figma)" || echo "No rate limit headers"
Step 2: Decision Tree
API returning errors?
├── 403 Forbidden
│ ├── Token expired (>90 days) → Rotate PAT immediately
│ ├── Wrong scopes → Regenerate with correct scopes
│ └── File not shared → Check file permissions
│
├── 429 Rate Limited
│ ├── Retry-After < 60s → Wait and retry automatically
│ ├── Retry-After > 300s → Reduce request volume
│ └── X-Figma-Rate-Limit-Type: low → Consider upgrading plan
│
├── 404 Not Found
│ ├── File deleted → Check with file owner
│ ├── Wrong file key → Verify FIGMA_FILE_KEY
│ └── API path wrong → Check endpoint documentation
│
├── 500/503 Server Error
│ ├── status.figma.com shows incident → Wait for resolution
│ ├── Intermittent → Retry with backoff
│ └── Persistent → Contact Figma support
│
└── Network Error (ECONNREFUSED, timeout)
├── DNS resolution failing → Check DNS config
├── Firewall blocking → Verify outbound HTTPS to api.figma.com
└── TLS error → Check Node.js version (18+ required)
Step 3: Immediate Mitigation
For 403 (Token Expired):
# Generate new PAT in Figma Settings > Personal access tokens
# Then update your deployment:
# GitHub Actions
gh secret set FIGMA_PAT --body "figd_new-token-here"
# Cloud Run
echo -n "figd_new-token" | gcloud secrets versions add figma-pat --data-file=-
gcloud run services update my-service --update-secrets="FIGMA_P'Set up Figma REST API authentication with personal access tokens or.
Figma Install & Auth
Overview
Configure authentication for the Figma REST API. Figma supports two auth methods: Personal Access Tokens (PATs) for scripts and server-side tools, and OAuth 2.0 for apps that act on behalf of users. All requests go to https://api.figma.com.
Prerequisites
- Figma account (Free, Professional, or Enterprise)
- Node.js 18+ (for JS/TS integrations)
- A Figma file key (the string after
/design/in a Figma URL)
Instructions
Step 1: Generate a Personal Access Token
- Open Figma > Settings > Account > Personal access tokens
- Click Generate new token
- Name the token and assign scopes:
| Scope | Access | Use Case |
|---|---|---|
file_content:read |
Read file JSON | Inspecting layers, extracting design tokens |
file_content:write |
Modify files | Programmatic design updates |
file_comments:read |
Read comments | Review tooling |
file_comments:write |
Post comments | Automated feedback |
filedevresources:read |
Dev resources | Dev mode integrations |
file_variables:read |
Read variables | Design token sync |
file_variables:write |
Write variables | Token pipeline |
webhooks:write |
Manage webhooks | Event-driven automation |
- Copy the token immediately -- it is shown only once
- PATs expire after a maximum of 90 days
Step 2: Store Credentials Securely
# .env (NEVER commit to git)
FIGMA_PAT="figd_your-personal-access-token"
FIGMA_FILE_KEY="abc123XYZdefaultFileKey"
# .gitignore
.env
.env.local
.env.*.local
Step 3: Verify Connection
# Test with curl -- should return your user profile
curl -s -H "X-Figma-Token: ${FIGMA_PAT}" \
https://api.figma.com/v1/me | jq '.handle, .email'
// verify-figma.ts
const PAT = process.env.FIGMA_PAT!;
const res = await fetch('https://api.figma.com/v1/me', {
headers: { 'X-Figma-Token': PAT },
});
if (!res.ok) throw new Error(`Figma auth failed: ${res.status}`);
const me = await res.json();
console.log(`Authenticated as ${me.handle} (${me.email})`);
Step 4: OAuth 2.0 (For User-Facing Apps)
Use OAuth when your app needs to act on behalf of other Figma users.
// 1. Redirect user to Figma authorization URL
const authUrl = new URL('https://'Avoid the most common Figma API integration mistakes and anti-patterns.
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.
'Load test Figma API integrations and plan for scale.
Figma Load & Scale
Overview
Test and plan for the throughput limits of your Figma API integration. Figma's rate limits use a leaky bucket algorithm -- this skill helps you find the bucket size for your plan tier and design your integration to stay within it.
Prerequisites
- k6 load testing tool (
brew install k6orapt install k6) - Figma test PAT (do not load test with production token)
- A test Figma file (not your production design system)
Instructions
Step 1: k6 Load Test Script
// figma-load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';
const figmaErrors = new Rate('figma_errors');
const figmaLatency = new Trend('figma_latency', true);
export const options = {
scenarios: {
// Test 1: Find your rate limit ceiling
rate_limit_probe: {
executor: 'constant-arrival-rate',
rate: 10, // 10 requests per second
timeUnit: '1s',
duration: '2m',
preAllocatedVUs: 5,
maxVUs: 20,
},
},
thresholds: {
figma_errors: ['rate<0.10'], // Less than 10% errors
figma_latency: ['p(95)<3000'], // P95 under 3 seconds
http_req_duration: ['p(99)<5000'], // P99 under 5 seconds
},
};
const PAT = __ENV.FIGMA_PAT;
const FILE_KEY = __ENV.FIGMA_FILE_KEY;
export default function () {
// Use a lightweight endpoint for rate limit testing
const res = http.get(
`https://api.figma.com/v1/files/${FILE_KEY}?depth=1`,
{
headers: { 'X-Figma-Token': PAT },
tags: { endpoint: 'files' },
}
);
figmaLatency.add(res.timings.duration);
const isError = res.status !== 200;
figmaErrors.add(isError);
check(res, {
'status is 200': (r) => r.status === 200,
'not rate limited': (r) => r.status !== 429,
'latency < 2s': (r) => r.timings.duration < 2000,
});
if (res.status === 429) {
const retryAfter = parseInt(res.headers['Retry-After'] || '60');
console.log(`Rate limited. Retry-After: ${retryAfter}s`);
sleep(retryAfter);
} else {
sleep(0.1); // 100ms between requests
}
}
Step 2: Run Load Tests
# Probe rate limits
k6 run \
--env FIGMA_PAT="${FIGMA_PAT}" \
--env FIGMA_FILE_KEY="${FIGMA_FILE_KEY}" \
figma-load-test.js
# Export results to JSON for analysis
k6 run \
--env FIGMA_PAT="${FIGMA_PAT}" \
--env FIGMA_FILE_KEY="${FIGMA_FILE_KEY}" \
--out json=results.json \
figma-load-test.js
Step 3: Capacity Planning
interface FigmaCapacityPlan {
planTier: string;
measuredLimitPerMinute: number;
currentUsag'Set up a local development workflow for Figma plugin and REST API projects.
Figma Local Dev Loop
Overview
Set up fast local development for two workflows: building Figma plugins that run inside the Figma editor, and building external apps that consume the Figma REST API.
Prerequisites
- Node.js 18+ with npm/pnpm
FIGMA_PATconfigured (seefigma-install-auth)- Figma desktop app (for plugin development)
Instructions
Step 1: REST API Project Structure
figma-integration/
├── src/
│ ├── figma-client.ts # Shared fetch wrapper
│ ├── extract-tokens.ts # Design token extraction
│ └── export-assets.ts # Asset export pipeline
├── tests/
│ ├── figma-client.test.ts
│ └── fixtures/ # Saved API responses for offline testing
│ └── sample-file.json
├── .env.local # FIGMA_PAT, FIGMA_FILE_KEY (git-ignored)
├── .env.example # Template for team
├── tsconfig.json
└── package.json
Step 2: Figma Plugin Project Structure
my-figma-plugin/
├── manifest.json # Plugin manifest (required by Figma)
├── code.ts # Plugin backend (runs in sandbox)
├── ui.html # Plugin UI (runs in iframe)
├── package.json
└── tsconfig.json
manifest.json (required):
{
"name": "My Plugin",
"id": "1234567890",
"api": "1.0.0",
"main": "dist/code.js",
"ui": "dist/ui.html",
"editorType": ["figma"],
"permissions": ["currentuser"]
}
Step 3: Plugin Development with Watch Mode
{
"scripts": {
"build": "esbuild code.ts --bundle --outfile=dist/code.js --target=es2020",
"watch": "esbuild code.ts --bundle --outfile=dist/code.js --target=es2020 --watch",
"dev": "concurrently \"npm run watch\" \"npm run watch:ui\"",
"watch:ui": "esbuild ui.tsx --bundle --outfile=dist/ui.html --loader:.html=copy --watch"
},
"devDependencies": {
"@figma/plugin-typings": "^1.0.0",
"esbuild": "^0.20.0",
"typescript": "^5.0.0"
}
}
Load the plugin in Figma:
- Figma desktop > Plugins > Development > Import plugin from manifest
- Select your
manifest.json - Run with
npm run watch-- changes auto-reload
Step 4: REST API Dev Loop with Testing
{
"scripts": {
"dev": "tsx watch src/extract-tokens.ts",
"test": "vitest",
"test:watch": "vitest --watch"
}
}
'Migrate design systems between Figma files, or from other tools to Figma.
Figma Migration Deep Dive
Overview
Automate migration of design data between Figma files, from other tools to Figma, or from Figma styles to the Variables API. Covers inventory, extraction, transformation, and validation.
Prerequisites
- Source and destination Figma file keys
FIGMAPATwithfilecontent:readandfile_variables:write(Enterprise) scopes- Understanding of source file structure
Instructions
Step 1: Inventory Source File
const PAT = process.env.FIGMA_PAT!;
async function inventoryFile(fileKey: string) {
const res = await fetch(
`https://api.figma.com/v1/files/${fileKey}`,
{ headers: { 'X-Figma-Token': PAT } }
);
const file = await res.json();
const inventory = {
name: file.name,
pages: file.document.children.map((p: any) => p.name),
componentCount: Object.keys(file.components).length,
styleCount: Object.keys(file.styles).length,
styles: {
fills: Object.values(file.styles).filter((s: any) => s.style_type === 'FILL').length,
text: Object.values(file.styles).filter((s: any) => s.style_type === 'TEXT').length,
effects: Object.values(file.styles).filter((s: any) => s.style_type === 'EFFECT').length,
grids: Object.values(file.styles).filter((s: any) => s.style_type === 'GRID').length,
},
};
// Count total nodes
let nodeCount = 0;
function countNodes(node: any) {
nodeCount++;
if (node.children) node.children.forEach(countNodes);
}
countNodes(file.document);
(inventory as any).totalNodes = nodeCount;
return inventory;
}
// Usage
const inv = await inventoryFile(process.env.FIGMA_FILE_KEY!);
console.log(`File: ${inv.name}`);
console.log(`Pages: ${inv.pages.join(', ')}`);
console.log(`Components: ${inv.componentCount}, Styles: ${inv.styleCount}`);
console.log(`Total nodes: ${(inv as any).totalNodes}`);
Step 2: Extract Styles from Source
async function extractAllStyles(fileKey: string) {
const file = await fetch(
`https://api.figma.com/v1/files/${fileKey}`,
{ headers: { 'X-Figma-Token': PAT } }
).then(r => r.json());
const styleNodeIds = Object.keys(file.styles);
const nodesRes = await fetch(
`https://api.figma.com/v1/files/${fileKey}/nodes?ids=${styleNodeIds.join(',')}`,
{ headers: { 'X-Figma-Token': PAT } }
).then(r => r.json());
const extracted = [];
for (const [nodeId, styleMeta] of Object.entries(file.styles) as any[]) {
const node = nodesRes.nodes[nodeId]?.document;
if (!node) continue;
extracted.push({
name: styleMeta.name,
type: styleMeta.style_type,
nodeId,
data: {
fills: node.fills,
strokes: node.strokes,
effects: node.effects,
styl'Configure Figma API access across dev, staging, and production environments.
Figma Multi-Environment Setup
Overview
Configure separate Figma API credentials and file targets per environment. Use different PATs with minimal scopes, point to different Figma files, and prevent accidental production operations from dev.
Prerequisites
- Separate Figma PATs for each environment
- Secret management solution
- Environment detection in application
Instructions
Step 1: Environment Strategy
| Environment | PAT Scopes | Figma File | Cache TTL |
|---|---|---|---|
| Development | file_content:read |
Copy of design file | 10s (fast iteration) |
| Staging | filecontent:read, filecomments:read |
Staging branch/file | 60s |
| Production | file_content:read, webhooks:write |
Production design file | 300s |
Step 2: Configuration by Environment
// src/config/figma.ts
interface FigmaEnvConfig {
token: string;
fileKey: string;
cacheTTL: number;
webhookPasscode?: string;
maxConcurrency: number;
}
function getFigmaConfig(): FigmaEnvConfig {
const env = process.env.NODE_ENV || 'development';
const configs: Record<string, Partial<FigmaEnvConfig>> = {
development: {
token: process.env.FIGMA_PAT_DEV!,
fileKey: process.env.FIGMA_FILE_KEY_DEV!,
cacheTTL: 10_000,
maxConcurrency: 1,
},
staging: {
token: process.env.FIGMA_PAT_STAGING!,
fileKey: process.env.FIGMA_FILE_KEY_STAGING!,
cacheTTL: 60_000,
maxConcurrency: 3,
},
production: {
token: process.env.FIGMA_PAT_PROD!,
fileKey: process.env.FIGMA_FILE_KEY_PROD!,
cacheTTL: 300_000,
maxConcurrency: 5,
webhookPasscode: process.env.FIGMA_WEBHOOK_PASSCODE,
},
};
const config = configs[env];
if (!config?.token) throw new Error(`Figma token not configured for env: ${env}`);
if (!config?.fileKey) throw new Error(`Figma file key not configured for env: ${env}`);
return config as FigmaEnvConfig;
}
Step 3: Environment Files
# .env.development
FIGMA_PAT_DEV="figd_dev-token-read-only"
FIGMA_FILE_KEY_DEV="devFileKey123"
# .env.staging
FIGMA_PAT_STAGING="figd_staging-token"
FIGMA_FILE_KEY_STAGING="stagingFileKey456"
# .env.production (stored in secret manager, not in repo)
FIGMA_PAT_PROD="figd_prod-token"
FIGMA_FILE_KEY_PROD="prodFileKey789"
FIGMA_WEBHOOK_PASSCODE="webhook-secret"
# .env.example (committed to repo as template)
FIGMA_PAT_DEV=
FIGMA_FILE_KEY_DEV=
Step 4: Secret Management
# GitHub Actions -- use environment-scoped 'Set up monitoring, metrics, and alerting for Figma API integrations.
Figma Observability
Overview
Monitor Figma REST API health with custom metrics, structured logging, and alerts. Track request latency, error rates, rate limit headroom, and cache hit rates.
Prerequisites
- Prometheus or compatible metrics backend (or use OpenTelemetry)
- Structured logging (pino, winston)
- Alerting system (PagerDuty, Slack, OpsGenie)
Instructions
Step 1: Instrumented Figma Client
// Wrap every Figma API call with metrics and logging
class InstrumentedFigmaClient {
private metrics = {
requests: 0,
errors: 0,
rateLimits: 0,
totalLatencyMs: 0,
};
async request<T>(path: string, token: string): Promise<T> {
const start = performance.now();
const endpoint = path.replace(/[a-zA-Z0-9]{15,}/, ':key'); // normalize
try {
const res = await fetch(`https://api.figma.com${path}`, {
headers: { 'X-Figma-Token': token },
});
const latencyMs = performance.now() - start;
this.metrics.requests++;
this.metrics.totalLatencyMs += latencyMs;
// Log every request with structured data
console.log(JSON.stringify({
service: 'figma',
endpoint,
status: res.status,
latencyMs: Math.round(latencyMs),
rateLimit: {
remaining: res.headers.get('X-RateLimit-Remaining'),
type: res.headers.get('X-Figma-Rate-Limit-Type'),
},
}));
if (res.status === 429) {
this.metrics.rateLimits++;
const retryAfter = parseInt(res.headers.get('Retry-After') || '60');
throw new FigmaRateLimitError(retryAfter);
}
if (!res.ok) {
this.metrics.errors++;
throw new FigmaApiError(res.status, await res.text());
}
return res.json();
} catch (error) {
if (!(error instanceof FigmaApiError)) {
this.metrics.errors++;
console.error(JSON.stringify({
service: 'figma',
endpoint,
error: error instanceof Error ? error.message : 'Unknown',
latencyMs: Math.round(performance.now() - start),
}));
}
throw error;
}
}
getMetrics() {
return {
...this.metrics,
avgLatencyMs: this.metrics.requests > 0
? Math.round(this.metrics.totalLatencyMs / this.metrics.requests)
: 0,
errorRate: this.metrics.requests > 0
? (this.metrics.errors / this.metrics.requests * 100).toFixed(1) + '%'
: '0%',
};
}
}
Step 2: Prometheus Metrics
import { Registry, Counter, Histogram, Gauge } from 'prom-client';
const registry = new Registry();
const figmaRequests = new Counter({
name: 'figma_api_requests_total',
help: 'Total Figma API requests',
labelNa'Optimize Figma REST API performance with caching, partial fetches, and.
Figma Performance Tuning
Overview
Optimize Figma REST API performance. Large Figma files can return multi-megabyte JSON responses. Key strategies: fetch only what you need, cache aggressively, and batch requests.
Prerequisites
- Working Figma API integration
- Understanding of your access patterns (which endpoints, how often)
Instructions
Step 1: Reduce Payload Size
// BAD: fetches the entire file tree (can be 10+ MB for large files)
const file = await fetch(`https://api.figma.com/v1/files/${fileKey}`, {
headers: { 'X-Figma-Token': token },
}).then(r => r.json());
// GOOD: use depth parameter to limit tree depth
// depth=1 returns only pages (CANVAS nodes), not their children
const fileMeta = await fetch(
`https://api.figma.com/v1/files/${fileKey}?depth=1`,
{ headers: { 'X-Figma-Token': token } }
).then(r => r.json());
// GOOD: fetch only specific nodes you need
const nodes = await fetch(
`https://api.figma.com/v1/files/${fileKey}/nodes?ids=${nodeIds.join(',')}`,
{ headers: { 'X-Figma-Token': token } }
).then(r => r.json());
// GOOD: use plugin_data or branch_data params only when needed
// By default, plugin data and branch data are NOT returned
Step 2: Response Caching
import { LRUCache } from 'lru-cache';
// File metadata changes rarely -- cache for 5 minutes
const fileCache = new LRUCache<string, any>({
max: 100,
ttl: 5 * 60 * 1000, // 5 minutes
});
async function getCachedFile(fileKey: string, token: string) {
const cached = fileCache.get(fileKey);
if (cached) return cached;
const file = await fetch(
`https://api.figma.com/v1/files/${fileKey}?depth=1`,
{ headers: { 'X-Figma-Token': token } }
).then(r => r.json());
fileCache.set(fileKey, file);
return file;
}
// Image URLs expire after 30 days -- cache them but with a shorter TTL
const imageUrlCache = new LRUCache<string, string>({
max: 1000,
ttl: 24 * 60 * 60 * 1000, // 1 day (well within 30-day expiry)
});
async function getCachedImageUrl(
fileKey: string, nodeId: string, format: string, token: string
): Promise<string | null> {
const cacheKey = `${fileKey}:${nodeId}:${format}`;
const cached = imageUrlCache.get(cacheKey);
if (cached) return cached;
const data = await fetch(
`https://api.figma.com/v1/images/${fileKey}?ids=${nodeId}&format=${format}`,
{ headers: { 'X-Figma-Token': token } }
).then(r => r.json());
const url = data.images[nodeId];
if (url) imageUrlCache.set(cacheKey, url);
return url;
}
Step 3: Webhook-Driven Cache Invalidation
// Instead of polling, use webhooks to know when to re-fetch
// See figma-webhooks-events for full webhook setup
async function handleFileUpdate(fileKey: string) {'Enforce security policies and coding standards for Figma API integrations.
Figma Policy & Guardrails
Overview
Automated guardrails for Figma API integrations: prevent token leaks, enforce scope minimization, validate webhook configurations, and catch common anti-patterns in CI.
Prerequisites
- ESLint or similar linter
- CI/CD pipeline (GitHub Actions)
- Pre-commit hooks infrastructure
Instructions
Step 1: Token Leak Prevention
# .pre-commit-config.yaml -- catch Figma tokens before commit
repos:
- repo: local
hooks:
- id: no-figma-tokens
name: Check for Figma PAT leaks
entry: bash -c '
if git diff --cached --diff-filter=ACM -z -- . |
xargs -0 grep -lP "figd_[a-zA-Z0-9_-]{20,}" 2>/dev/null; then
echo "ERROR: Figma PAT found in staged files"
echo "Store tokens in .env files (which should be in .gitignore)"
exit 1
fi
'
language: system
pass_filenames: false
# GitHub Actions secret scanning
# .github/workflows/figma-security.yml
name: Figma Security Check
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Scan for Figma tokens
run: |
if grep -rP "figd_[a-zA-Z0-9_-]{20,}" \
--include="*.ts" --include="*.js" --include="*.json" \
--exclude-dir=node_modules .; then
echo "::error::Figma PAT found in source code"
exit 1
fi
- name: Check .env files not committed
run: |
if git ls-files --cached | grep -E '^\.(env|env\.local|env\.production)$'; then
echo "::error::.env file committed to repository"
exit 1
fi
Step 2: ESLint Rules for Figma
// eslint-rules/no-figma-token-literal.js
module.exports = {
meta: {
type: 'problem',
docs: { description: 'Disallow hardcoded Figma PATs' },
},
create(context) {
return {
Literal(node) {
if (typeof node.value === 'string' && /^figd_[a-zA-Z0-9_-]{20,}/.test(node.value)) {
context.report({
node,
message: 'Hardcoded Figma PAT detected. Use process.env.FIGMA_PAT instead.',
});
}
},
TemplateLiteral(node) {
for (const quasi of node.quasis) {
if (/figd_[a-zA-Z0-9_-]{20,}/.test(quasi.value.raw)) {
context.report({
node,
message: 'Hardcoded Figma PAT in template literal.',
});
}
}
},
};
},
};
Step 3: API Usage Policies
// Runtime guardrails for Figma API usage
// 'Production readiness checklist for Figma REST API integrations.
Figma Production Checklist
Overview
Complete checklist for deploying Figma API integrations to production, covering authentication, error handling, rate limits, monitoring, and rollback.
Prerequisites
- Staging environment tested and verified
- Production PAT or OAuth credentials ready
- Monitoring infrastructure available
Instructions
Step 1: Authentication & Secrets
- [ ] Production PAT stored in secret manager (not env files)
- [ ] PAT uses minimum required scopes (
file_content:read, notfiles:read) - [ ] PAT expiry tracked (max 90 days) with rotation reminder
- [ ] OAuth refresh token flow tested (if using OAuth)
- [ ] Separate tokens for dev/staging/prod
- [ ] No tokens in client-side code or git history
Step 2: Error Handling
- [ ] All HTTP status codes handled (400, 403, 404, 429, 500)
- [ ]
Retry-Afterheader honored on 429 responses - [ ] Exponential backoff with jitter for transient errors
- [ ] Max retry limit to prevent infinite loops
- [ ] Graceful degradation when Figma is unavailable
- [ ] Error responses do not leak token values in logs
Step 3: Rate Limiting
- [ ] Request queue with concurrency control (max 3-5 concurrent)
- [ ] Batch node IDs in single requests (up to 50 per call)
- [ ] Response caching for frequently accessed files (TTL: 60-300s)
- [ ] Rate limit monitor with proactive throttling
- [ ] No tight loops calling Figma API without delays
Step 4: Monitoring & Health
// Health check endpoint
async function figmaHealthCheck() {
const start = Date.now();
try {
const res = await fetch('https://api.figma.com/v1/me', {
headers: { 'X-Figma-Token': process.env.FIGMA_PAT! },
signal: AbortSignal.timeout(5000),
});
return {
status: res.ok ? 'healthy' : 'degraded',
latencyMs: Date.now() - start,
httpStatus: res.status,
};
} catch (error) {
return {
status: 'unhealthy',
latencyMs: Date.now() - start,
error: error instanceof Error ? error.message : 'Unknown',
};
}
}
- [ ] Health endpoint includes Figma connectivity check
- [ ] Alerts on sustained 429 errors (>5/min)
- [ ] Alerts on 403 errors (token expiry)
- [ ] Alerts on response latency >5s (P95)
- [ ] Dashboard tracks requests/min, error rate, latency
Step 5: Data Handling
- [ ] Image export URLs treated as temporary (expire after 30 days)
- [ ] No PII from Figma stored without user consent
- [ ] File data cached with appropriate TTL
- [ ] Large file responses streamed, not buffered entirely in memory
Step 6: We
'Handle Figma REST API rate limits with exponential backoff and request.
Figma Rate Limits
Overview
Figma uses a leaky bucket algorithm for rate limiting. When the bucket is full, the API returns 429 with a Retry-After header. Limits vary by plan tier, seat type, and endpoint tier.
Prerequisites
- Figma REST API integration working
- Understanding of async/await patterns
Instructions
Step 1: Understand the Rate Limit Model
Endpoint tiers (limits are per-user, per-minute):
| Tier | Endpoints | Typical Limit |
|---|---|---|
| Tier 1 | GET /v1/files, GET /v1/images |
Higher quota |
| Tier 2 | GET /v1/files/:key/comments, GET /v1/files/:key/variables/local |
Moderate quota |
| Tier 3 | GET /v1/teams/:id/components, GET /v1/teams/:id/styles |
Lower quota |
429 response headers:
| Header | Type | Meaning |
|---|---|---|
Retry-After |
Integer (seconds) | Wait this long before retrying |
X-Figma-Plan-Tier |
String | Your Figma plan level |
X-Figma-Rate-Limit-Type |
String | "low" or "high" rate limit |
X-Figma-Upgrade-Link |
String | URL to upgrade for higher limits |
Step 2: Implement Exponential Backoff
async function figmaFetchWithRetry(
path: string,
token: string,
maxRetries = 5
): Promise<any> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await fetch(`https://api.figma.com${path}`, {
headers: { 'X-Figma-Token': token },
});
if (res.status === 429) {
const retryAfter = parseInt(res.headers.get('Retry-After') || '60');
const limitType = res.headers.get('X-Figma-Rate-Limit-Type') || 'unknown';
if (attempt === maxRetries) {
throw new Error(`Rate limited after ${maxRetries} retries (${limitType})`);
}
// Use the Retry-After header -- Figma tells you exactly how long to wait
const jitter = Math.random() * 1000;
const delay = retryAfter * 1000 + jitter;
console.warn(`429 (${limitType}). Waiting ${(delay/1000).toFixed(1)}s (attempt ${attempt + 1})`);
await new Promise(r => setTimeout(r, delay));
continue;
}
if (res.status >= 500 && attempt < maxRetries) {
// Server errors: exponential backoff without Retry-After
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
await new Promise(r => setTimeout(r, delay));
continue;
}
if 'Reference architecture for production Figma API integrations.
Figma Reference Architecture
Overview
Production-ready architecture for Figma REST API integrations. Covers the three most common use cases: design token pipelines, asset export systems, and webhook-driven automation.
Prerequisites
- Understanding of Figma REST API endpoints
- TypeScript project setup
- Decision on deployment platform
Instructions
Step 1: Project Structure
figma-integration/
├── src/
│ ├── figma/
│ │ ├── client.ts # Typed REST API wrapper
│ │ ├── types.ts # Figma API response types
│ │ ├── errors.ts # FigmaApiError, FigmaRateLimitError
│ │ ├── cache.ts # LRU cache for API responses
│ │ └── walker.ts # Node tree traversal utilities
│ ├── services/
│ │ ├── token-extractor.ts # Design token extraction
│ │ ├── asset-exporter.ts # Image/icon export pipeline
│ │ ├── comment-syncer.ts # Comment sync to Slack/Jira
│ │ └── variable-syncer.ts # Variables API sync (Enterprise)
│ ├── webhooks/
│ │ ├── handler.ts # Webhook event router
│ │ ├── verify.ts # Passcode verification
│ │ └── processors/
│ │ ├── file-update.ts # FILE_UPDATE handler
│ │ ├── comment.ts # FILE_COMMENT handler
│ │ └── library.ts # LIBRARY_PUBLISH handler
│ ├── api/
│ │ ├── health.ts # Health check endpoint
│ │ ├── tokens.ts # Token API endpoint
│ │ └── assets.ts # Asset download endpoint
│ └── index.ts
├── scripts/
│ ├── extract-tokens.mjs # CLI: extract tokens from Figma
│ ├── export-icons.mjs # CLI: export icons from Figma
│ └── setup-webhooks.mjs # CLI: create/manage webhooks
├── output/
│ ├── tokens.css # Generated CSS custom properties
│ ├── tokens.json # Generated JSON tokens
│ └── icons/ # Exported SVG/PNG icons
├── tests/
│ ├── fixtures/ # Saved Figma API responses
│ └── *.test.ts
├── .env.example
└── package.json
Step 2: Data Flow Architecture
┌────────────────────────────────────────────────┐
│ Figma Cloud │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Files API │ │Images API│ │ Webhooks V2 │ │
│ │ /v1/files │ │/v1/images│ │ /v2/webhooks │ │
│ └─────┬─────┘ └────┬─────┘ └──────┬───────┘ │
└────────┼──────────────┼───────────────┼─────────┘
│ │ │
┌────▼────┐ ┌────▼────┐ ┌─────▼────┐
│ Token │ │ Asset │ │ Webhook │
│Extractor│ │Exporter │ │ Handler │
└────┬────┘ └────┬────┘ └─────┬────┘
│ │ │
┌────▼────┐ ┌────▼────┐ ┌─────▼────┐
│ Cache │ │ Cache │ │ Event │
│ (LRU) │ │ (URLs) │ │ Queue │
└────┬────┘ └────┬────┘ └────'Build resilient Figma integrations with circuit breakers, fallbacks,.
Figma Reliability Patterns
Overview
Production reliability patterns for Figma REST API integrations. Figma is an external dependency -- your application must handle its outages, rate limits, and slow responses without cascading failures.
Prerequisites
- Working Figma API integration
- Understanding of circuit breaker pattern
- Cache or file system for fallback data
Instructions
Step 1: Circuit Breaker
// Prevent cascading failures when Figma is down
class FigmaCircuitBreaker {
private failures = 0;
private lastFailure = 0;
private state: 'closed' | 'open' | 'half-open' = 'closed';
constructor(
private threshold = 5, // Open after 5 failures
private resetTimeMs = 30_000 // Try again after 30s
) {}
async execute<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === 'open') {
if (Date.now() - this.lastFailure > this.resetTimeMs) {
this.state = 'half-open';
console.log('[figma-circuit] State: half-open (testing recovery)');
} else {
throw new Error('Figma circuit breaker is OPEN -- failing fast');
}
}
try {
const result = await fn();
if (this.state === 'half-open') {
this.state = 'closed';
this.failures = 0;
console.log('[figma-circuit] State: closed (recovered)');
}
return result;
} catch (error) {
this.failures++;
this.lastFailure = Date.now();
if (this.failures >= this.threshold) {
this.state = 'open';
console.warn(`[figma-circuit] State: OPEN after ${this.failures} failures`);
}
throw error;
}
}
getState() { return this.state; }
}
const figmaBreaker = new FigmaCircuitBreaker();
// Usage
async function safeFigmaCall<T>(fn: () => Promise<T>): Promise<T> {
return figmaBreaker.execute(fn);
}
Step 2: Cached Fallback
import { readFileSync, writeFileSync, existsSync } from 'fs';
// Serve cached data when Figma is unavailable
class FigmaFallbackCache {
constructor(private cacheDir = '.figma-cache') {}
private getPath(key: string) {
return `${this.cacheDir}/${key.replace(/[^a-zA-Z0-9]/g, '_')}.json`;
}
save(key: string, data: any) {
const { mkdirSync } = require('fs');
mkdirSync(this.cacheDir, { recursive: true });
writeFileSync(this.getPath(key), JSON.stringify({
data,
cachedAt: new Date().toISOString(),
}));
}
load(key: string): { data: any; cachedAt: string } | null {
const path = this.getPath(key);
if (!existsSync(path)) return null;
return JSON.parse(readFileSync(path, 'utf-8'));
}
}
const fallbackCache = new FigmaFallbackCache();
async'Production-ready patterns for the Figma REST API and Plugin API.
Figma SDK Patterns
Overview
Production patterns for the Figma REST API (external tools) and Plugin API (in-editor plugins). Figma has no official Node.js SDK -- you call https://api.figma.com directly with fetch. These patterns give you type safety, error handling, and reusable abstractions.
Prerequisites
FIGMA_PATenvironment variable set- TypeScript 5+ project
- Understanding of Figma node types
Instructions
Step 1: Typed REST API Client
// src/figma-client.ts
export class FigmaClient {
private baseUrl = 'https://api.figma.com';
constructor(private token: string) {
if (!token) throw new Error('Figma token is required');
}
private async request<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${this.baseUrl}${path}`, {
...init,
headers: {
'X-Figma-Token': this.token,
'Content-Type': 'application/json',
...init?.headers,
},
});
if (res.status === 429) {
const retryAfter = parseInt(res.headers.get('Retry-After') || '60');
throw new FigmaRateLimitError(retryAfter);
}
if (res.status === 403) throw new FigmaAuthError('Invalid or expired token');
if (res.status === 404) throw new FigmaNotFoundError(path);
if (!res.ok) throw new FigmaApiError(res.status, await res.text());
return res.json();
}
async getFile(fileKey: string) {
return this.request<FigmaFileResponse>(`/v1/files/${fileKey}`);
}
async getFileNodes(fileKey: string, nodeIds: string[]) {
const ids = encodeURIComponent(nodeIds.join(','));
return this.request<FigmaNodesResponse>(`/v1/files/${fileKey}/nodes?ids=${ids}`);
}
async getImages(fileKey: string, nodeIds: string[], opts?: ImageOptions) {
const params = new URLSearchParams({
ids: nodeIds.join(','),
format: opts?.format ?? 'png',
scale: String(opts?.scale ?? 2),
});
return this.request<FigmaImagesResponse>(`/v1/images/${fileKey}?${params}`);
}
async getComments(fileKey: string) {
return this.request<FigmaCommentsResponse>(`/v1/files/${fileKey}/comments`);
}
async postComment(fileKey: string, message: string, nodeId?: string) {
return this.request(`/v1/files/${fileKey}/comments`, {
method: 'POST',
body: JSON.stringify({
message,
...(nodeId && { client_meta: { node_id: nodeId } }),
}),
});
}
async getLocalVariables(fileKey: string) {
return this.request<FigmaVariablesResponse>(
`/v1/files/${fileKey}/variables/local`
);
}
}
Step 2: Custom Error Classes
// src/figma-errors.ts
export class FigmaApiError extends Error {
constru'Secure Figma API tokens, configure scopes, and validate webhook signatures.
Figma Security Basics
Overview
Secure your Figma API integration: store tokens safely, apply least-privilege scopes, rotate credentials, and verify webhook signatures.
Prerequisites
- Figma PAT or OAuth app configured
- Understanding of environment variables
.gitignoreconfigured for secret files
Instructions
Step 1: Token Storage
# .env (NEVER commit)
FIGMA_PAT="figd_your-personal-access-token"
FIGMA_OAUTH_CLIENT_SECRET="your-oauth-secret"
# .gitignore
.env
.env.local
.env.*.local
*.pem
// Validate token exists before any API call
function getToken(): string {
const token = process.env.FIGMA_PAT;
if (!token) throw new Error('FIGMA_PAT is not set');
if (!token.startsWith('figd_')) {
console.warn('Token does not have expected figd_ prefix');
}
return token;
}
Step 2: Least-Privilege Scopes
Assign the minimum scopes needed for each use case:
| Use Case | Required Scopes |
|---|---|
| Read file structure | file_content:read |
| Export images | file_content:read |
| Post comments | file_comments:write |
| Read variables (Enterprise) | file_variables:read |
| Manage webhooks | webhooks:write |
| Read team components | teamlibrarycontent:read |
| Dev mode resources | filedevresources:read |
Deprecated scope: files:read is deprecated. Use specific scopes like filecontent:read, filecomments:read instead.
Step 3: Token Rotation
# PATs have a maximum 90-day lifetime
# Schedule rotation before expiry
# 1. Generate new token in Figma Settings > Personal access tokens
# 2. Test new token
curl -s -H "X-Figma-Token: ${NEW_TOKEN}" \
https://api.figma.com/v1/me | jq '.handle'
# 3. Update environment
# For CI: gh secret set FIGMA_PAT --body "${NEW_TOKEN}"
# For production: update your secret manager
# 4. Verify old token is revoked in Figma Settings
Step 4: Webhook Passcode Verification
Figma webhooks use a passcode field (not HMAC signatures) for verification:
// When creating a webhook, you provide a passcode:
// POST /v2/webhooks
// { "event_type": "FILE_UPDATE", "team_id": "...", "endpoint": "...", "passcode": "my-secret" }
// Figma sends the passcode back in the webhook payloa'Handle Figma REST API scope changes, deprecations, and migration tasks.
Figma Upgrade & Migration
Overview
Handle Figma REST API deprecations and breaking changes. The most significant recent change is the deprecation of the files:read scope in favor of granular scopes, and the move from Webhooks V1 to V2.
Prerequisites
- Current Figma integration working
- Git for version control
- Access to Figma developer settings
Instructions
Step 1: Scope Migration (files:read Deprecation)
The files:read scope is deprecated. Migrate to granular scopes:
| Deprecated Scope | Replacement Scopes | Endpoints Covered |
|---|---|---|
files:read |
file_content:read |
GET /v1/files/:key, GET /v1/images/:key |
files:read |
file_comments:read |
GET /v1/files/:key/comments |
files:read |
filedevresources:read |
GET /v1/files/:key/dev_resources |
files:read |
file_versions:read |
GET /v1/files/:key/versions |
Migration steps:
- Audit which endpoints your code calls
- Map each endpoint to its required scope
- Generate a new PAT with granular scopes
- Update OAuth apps with new scope list
- Test all endpoints with the new token
- Revoke old tokens
# Find all Figma API calls in your codebase
grep -rn "api.figma.com" --include="*.ts" --include="*.js" src/ \
| grep -oP '/v\d/[a-z_/]+' | sort -u
# Example output:
# /v1/files
# /v1/files/comments
# /v1/images
# /v2/webhooks
Step 2: Webhooks V1 to V2 Migration
// V1 (deprecated): POST /v1/webhooks
// V2 (current): POST /v2/webhooks
// V2 adds context support: attach webhooks to teams, files, or projects
interface WebhookV2Config {
event_type: 'FILE_UPDATE' | 'FILE_DELETE' | 'FILE_VERSION_UPDATE'
| 'FILE_COMMENT' | 'LIBRARY_PUBLISH';
// Context: where to listen
team_id?: string; // team-level (all files in team)
// OR specify project/file context in the endpoint path
endpoint: string; // Your HTTPS webhook URL
passcode: string; // Secret for verification
description?: string;
}
// Create a V2 webhook
async function createWebhook(config: WebhookV2Config) {
const res = await fetch('https://api.figma.com/v2/webhooks', {
method: 'POST',
headers: {
'X-Figma-Token': process.env.FIGMA_PAT!,
'Content-Type': 'application/json',
},
body: JSON.stringify(config),
});
if (!res.ok) throw 'Implement Figma Webhooks V2 for real-time file, comment, and library.
Figma Webhooks & Events
Overview
Figma Webhooks V2 push real-time notifications when files change, comments are posted, or libraries are published. Webhooks can be scoped to teams, projects, or individual files. Authentication uses a passcode echoed back in each payload.
Prerequisites
- HTTPS endpoint accessible from the internet
FIGMA_PATwithwebhooks:writescope- Team ID (from Figma URL:
figma.com/files/team/)/...
Instructions
Step 1: Create a Webhook
# POST /v2/webhooks -- requires webhooks:write scope
curl -X POST https://api.figma.com/v2/webhooks \
-H "X-Figma-Token: ${FIGMA_PAT}" \
-H "Content-Type: application/json" \
-d '{
"event_type": "FILE_UPDATE",
"team_id": "123456789",
"endpoint": "https://yourapp.com/webhooks/figma",
"passcode": "your-secret-passcode",
"description": "Sync design tokens on file update"
}'
# Response:
# { "id": "wh_abc123", "event_type": "FILE_UPDATE", "status": "ACTIVE", ... }
Available event types:
| Event Type | Trigger | Payload Contains |
|---|---|---|
FILE_UPDATE |
File saved to version history | filekey, filename, timestamp |
FILE_DELETE |
File deleted | filekey, filename |
FILEVERSIONUPDATE |
Named version created | filekey, versionid, label |
FILE_COMMENT |
Comment added | filekey, comment, commentid |
LIBRARY_PUBLISH |
Library published | file_key, description, variables |
Step 2: Handle Webhook Events
import express from 'express';
import crypto from 'crypto';
const app = express();
app.use(express.json());
// Figma webhook payload types
interface FigmaWebhookBase {
event_type: string;
passcode: string;
timestamp: string;
webhook_id: string;
}
interface FileUpdateEvent extends FigmaWebhookBase {
event_type: 'FILE_UPDATE';
file_key: string;
file_name: string;
triggered_by: { id: string; handle: string };
}
interface FileCommentEvent extends FigmaWebhookBase {
event_type: 'FILE_COMMENT';
file_key: string;
file_name: string;
comment: Array<{ text: string }>;
comment_id: string;
triggered_by: {How It Works
/plugin install figma-pack@claude-code-plugins-plus
export FIGMA_PAT="figd_your-personal-access-token"
export FIGMA_FILE_KEY="your-file-key-from-figma-url"
"Help me extract design tokens from my Figma file"
"Export all icons from my Figma components as SVG"
"Set up a webhook to sync tokens when my Figma file changes"
Ready to use figma-pack?
Related Plugins
supabase-pack
Complete Supabase integration skill pack with 30 skills covering authentication, database, storage, realtime, edge functions, and production operations. Flagship+ tier vendor pack.
/plugin install supabase-pack@claude-code-plugins-plus
vercel-pack
Complete Vercel integration skill pack with 30 skills covering deployments, edge functions, preview environments, performance optimization, and production operations. Flagship+ tier vendor pack.
/plugin install vercel-pack@claude-code-plugins-plus
clay-pack
Complete Clay integration skill pack with 30 skills covering data enrichment, waterfall workflows, AI agents, and GTM automation. Flagship+ tier vendor pack.
/plugin install clay-pack@claude-code-plugins-plus
cursor-pack
Complete Cursor integration skill pack with 30 skills covering AI code editing, composer workflows, codebase indexing, and productivity features. Flagship+ tier vendor pack.
/plugin install cursor-pack@claude-code-plugins-plus
exa-pack
Complete Exa integration skill pack with 30 skills covering neural search, semantic retrieval, web search API, and AI-powered discovery. Flagship+ tier vendor pack.
/plugin install exa-pack@claude-code-plugins-plus
firecrawl-pack
Complete Firecrawl integration skill pack with 30 skills covering web scraping, crawling, markdown conversion, and LLM-ready data extraction. Flagship+ tier vendor pack.
/plugin install firecrawl-pack@claude-code-plugins-plus