Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Handle Figma REST API scope changes, deprecations, and migration tasks. Use when migrating from deprecated scopes, updating webhook versions, or adapting to Figma API changelog changes. Trigger with phrases like "upgrade figma", "figma deprecation", "figma scope migration", "figma API changes", "figma v2 webhooks".
.claude/skills/jeremylongshore-figma-upgrade-migration/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 1% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 29% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 108% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 48% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 68% | 0% |
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.
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 | file_dev_resources:read | GET /v1/files/:key/dev_resources | | files:read | file_versions:read | GET /v1/files/:key/versions |
Migration steps:
bash# 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
typescript// 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 new Error(`Webhook creation failed: ${res.status}`); return res.json(); } // List existing webhooks async function listWebhooks(teamId: string) { const res = await fetch( `https://api.figma.com/v2/webhooks?team_id=${teamId}`, { headers: { 'X-Figma-Token': process.env.FIGMA_PAT! } } ); return res.json(); }
All OAuth apps (public and private) must complete the new publishing flow:
typescript// Check if your OAuth tokens need refresh async function checkTokenHealth(accessToken: string): Promise<boolean> { const res = await fetch('https://api.figma.com/v1/me', { headers: { 'X-Figma-Token': accessToken }, }); if (res.status === 403) { console.warn('Token expired or revoked -- refresh needed'); return false; } return res.ok; }
typescript// Create a migration checker function auditFigmaIntegration(codebasePaths: string[]) { const issues: string[] = []; // Check for deprecated scope usage // Check for V1 webhook endpoints // Check for old token format const patterns = [ { pattern: 'files:read', message: 'Deprecated scope: use file_content:read' }, { pattern: '/v1/webhooks', message: 'V1 webhooks: migrate to /v2/webhooks' }, { pattern: 'X-FIGMA-TOKEN', message: 'Header is case-sensitive: use X-Figma-Token' }, ]; return { issues, patterns }; }
files:read to granular alternatives| Issue | Cause | Solution | |-------|-------|----------| | 403 after scope change | Missing required scope | Add the specific scope for each endpoint | | Webhook not firing | V1 webhook still active | Delete V1, create V2 webhook | | OAuth flow broken | Publishing flow not completed | Complete app publishing in dashboard | | Token format mismatch | Old token type | Generate new PAT with figd_ prefix |
Find every deprecated files:read scope reference before Figma sunsets it (Step 1 + Step 4 audit):
bash/usr/bin/grep -rn "files:read" --include='*.{ts,js,json,yml}' . | /usr/bin/grep -v node_modules
textsrc/auth/oauth.ts:12: scope: 'files:read', ← replace with file_content:read config/figma-app.json:8: "scopes": ["files:read"] ← update in the Figma app config too
Migrate a V1 webhook to V2 and confirm the new shape (Step 2):
bashcurl -s -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":"'"${FIGMA_TEAM_ID}"'","endpoint":"https://example.com/figma/webhook","passcode":"'"${WEBHOOK_PASSCODE}"'"}' \ | jq '{id, event_type, status}'
Full deprecation table and OAuth publishing steps: references/scope-migration-files-read-deprecation.md, references/oauth-app-publishing-flow.md.
For CI integration during upgrades, see figma-ci-integration.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 24,492 | 19,462 | -21% | 1 | 1 | 0% | 5,121 | 5,151 | +1% | 0 | 0 | — |
case-02 | fail→pass | 23,088 | 17,457 | -24% | 1 | 1 | 0% | 4,247 | 5,475 | +29% | 0 | 0 | — |
case-03 | pass→pass | 6,301 | 3,858 | -39% | 1 | 1 | 0% | 1,196 | 2,538 | +112% | 0 | 0 | — |
case-04 | pass→pass | 11,491 | 6,221 | -46% | 1 | 1 | 0% | 2,015 | 2,751 | +37% | 0 | 0 | — |
case-05 | fail→pass | 5,644 | 2,965 | -47% | 1 | 1 | 0% | 1,100 | 2,290 | +108% | 0 | 0 | — |
case-06 | pass→pass | 5,316 | 5,238 | -1% | 1 | 1 | 0% | 1,030 | 2,570 | +150% | 0 | 0 | — |
case-07 | pass→pass | 6,724 | 3,755 | -44% | 1 | 1 | 0% | 1,086 | 2,315 | +113% | 0 | 0 | — |
case-08 | fail→pass | 9,490 | 4,652 | -51% | 1 | 1 | 0% | 1,738 | 2,575 | +48% | 0 | 0 | — |
case-09 | pass→pass | 8,512 | 4,770 | -44% | 1 | 1 | 0% | 1,582 | 2,684 | +70% | 0 | 0 | — |
case-10 | pass→pass | 5,324 | 4,866 | -9% | 1 | 1 | 0% | 995 | 2,779 | +179% | 0 | 0 | — |
case-11 | pass→pass | 11,310 | 8,123 | -28% | 1 | 1 | 0% | 1,922 | 3,243 | +69% | 0 | 0 | — |
case-12 | pass→pass | 11,134 | 10,980 | -1% | 1 | 1 | 0% | 1,873 | 3,460 | +85% | 0 | 0 | — |
case-13 | pass→pass | 12,310 | 931,664 | +7468% | 1 | 1 | 0% | 1,774 | 3,042 | +71% | 0 | 0 | — |
case-14 | fail→pass | 16,252 | 918,317 | +5550% | 1 | 1 | 0% | 2,278 | 3,819 | +68% | 0 | 0 | — |
case-15 | pass→pass | 6,511 | 2,542 | -61% | 1 | 1 | 0% | 1,241 | 2,215 | +78% | 0 | 0 | — |
case-16 | fail→pass | 11,274 | 4,745 | -58% | 1 | 1 | 0% | 2,075 | 2,711 | +31% | 0 | 0 | — |
case-17 | fail→fail | 11,653 | 3,982 | -66% | 1 | 1 | 0% | 1,983 | 2,466 | +24% | 0 | 0 | — |
case-18 | pass→fail | 8,235 | 8,618 | +5% | 1 | 1 | 0% | 1,612 | 3,404 | +111% | 0 | 0 | — |
case-19 | pass→pass | 12,957 | 12,450 | -4% | 1 | 1 | 0% | 2,358 | 4,049 | +72% | 0 | 0 | — |
case-20 | pass→pass | 15,139 | 13,998 | -8% | 1 | 1 | 0% | 2,799 | 4,308 | +54% | 0 | 0 | — |
case-21 | pass→pass | 4,600 | 4,005 | -13% | 1 | 1 | 0% | 810 | 2,476 | +206% | 0 | 0 | — |
case-22 | pass→pass | 6,213 | 4,668 | -25% | 1 | 1 | 0% | 1,267 | 2,693 | +113% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.