
Figma Developer
- 77 installs
- 14 repo stars
- Updated March 2, 2026
- oakoss/agent-skills
Helps with design & ui/ux tasks during AI-assisted development.
About
figma-developer is a Claude Code skill for design & ui/ux. It helps solo builders move faster with AI-assisted coding.
- figma-developer
- Design & UI/UX
- AI-coding skill
Figma Developer by the numbers
- 77 all-time installs (skills.sh)
- +2 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,175 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/oakoss/agent-skills --skill figma-developerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 77 |
|---|---|
| repo stars | ★ 14 |
| Last updated | March 2, 2026 |
| Repository | oakoss/agent-skills ↗ |
What it does
Helps with design & ui/ux tasks during AI-assisted development.
Files
Figma Developer
Overview
Automates the Figma-to-code bridge using the Figma REST API. Extracts design tokens (colors, typography, spacing) as CSS variables and JSON, exports icons as SVG React components, generates React components from Figma node structures, and sets up CI pipelines for continuous design sync.
When to use: Syncing design tokens from Figma to code, exporting icon sets as React components, generating component scaffolding from Figma variants, setting up automated design sync in CI.
When NOT to use: Manual one-off design tweaks (just copy values), Figma plugin development (use the Plugin API instead), real-time collaborative editing (use Figma webhooks directly), or projects without a Figma design system.
Quick Reference
| Pattern | API / Approach | Key Points |
|---|---|---|
| Get file data | GET /v1/files/:key | Returns full document tree with styles and fills |
| Get specific nodes | GET /v1/files/:key/nodes?ids=... | Fetch only the nodes you need to reduce payload |
| Export images | GET /v1/images/:key?ids=...&format=svg | Renders nodes as SVG/PNG/PDF; URLs expire in 14 days |
| Get styles | GET /v1/files/:key/styles | Lists all published color, text, and effect styles |
| Get components | GET /v1/files/:key/components | Lists all published components with metadata |
| Get component sets | GET /v1/files/:key/component_sets | Returns variant groupings for components |
| Get variables | GET /v1/files/:key/variables/local | Extracts Figma Variables (colors, spacing, etc.) |
| Publish variables | POST /v1/variables | Publishes local variables organization-wide |
| Token extraction | Parse styles from file response, transform to CSS/JSON | No built-in "extract tokens" endpoint exists |
| CI sync | GitHub Actions cron + peter-evans/create-pull-request | Auto-sync PRs on Figma file changes |
Figma File Organization
| Section | Contents |
|---|---|
| Colors | All color styles with / hierarchy (Primary/500) |
| Typography | Text styles (Heading/Large, Body/Regular) |
| Spacing | Spacing guide values |
| Components | Variant groups (Button/Primary, Card/Default) |
| Icons | All icons in one exportable frame |
Common Mistakes
| Mistake | Fix |
|---|---|
Using fabricated API methods like extractDesignTokens() | Parse styles from GET /v1/files/:key response manually |
| Hardcoding Figma values instead of using tokens | Extract tokens to CSS variables, reference with var() |
| Not normalizing Figma style names | Lowercase + replace special chars with hyphens |
| Forgetting Figma uses 0-1 RGB not 0-255 | Multiply by 255 and round before hex conversion |
| No API response caching | Cache with TTL to avoid rate limits |
| Manual icon exports | Automate with GET /v1/images/:key + React component generation |
| No CI sync workflow | GitHub Actions cron + create-pull-request action |
| Using expired image URLs | Image export URLs expire in 14 days; re-fetch before use |
Libraries
| Package | Purpose |
|---|---|
figma-api | Community Figma REST API client (typed) |
@figma/rest-api-spec | Official TypeScript types for Figma API |
style-dictionary | Transform design tokens across platforms |
svgo | Optimize exported SVGs before use |
@svgr/core | Convert SVG files to React components |
Delegation
- Design systems: see
design-systemskill - Asset optimization: see
asset-managerskill
References
- Setup and Authentication
- Design Tokens
- Asset Export
- Component Generation
- CI Automation
- Troubleshooting
Asset Export
Export Icons as SVG
Use the Figma Images API (GET /v1/images/:key) to export nodes as SVG:
import { Api } from 'figma-api';
import fs from 'fs/promises';
async function exportIcons() {
const api = new Api({
personalAccessToken: process.env.FIGMA_ACCESS_TOKEN,
});
const fileKey = 'YOUR_FIGMA_FILE_KEY';
const file = await api.getFile({ file_key: fileKey });
const iconsFrame = findNode(file.document, 'Icons');
if (!iconsFrame || !iconsFrame.children) {
throw new Error('Icons frame not found');
}
const iconIds = iconsFrame.children.map((child: any) => child.id);
const { images } = await api.getImages({
file_key: fileKey,
queryParams: {
ids: iconIds.join(','),
format: 'svg',
},
});
for (const [nodeId, url] of Object.entries(images)) {
if (!url) continue;
const response = await fetch(url);
const content = await response.text();
const node = iconsFrame.children.find((c: any) => c.id === nodeId);
const name = normalizeFileName(node?.name ?? nodeId);
await fs.writeFile(`public/icons/${name}.svg`, content);
}
}Generate React Icon Components
async function generateIconComponents() {
const api = new Api({
personalAccessToken: process.env.FIGMA_ACCESS_TOKEN,
});
const fileKey = 'YOUR_FIGMA_FILE_KEY';
const file = await api.getFile({ file_key: fileKey });
const iconsFrame = findNode(file.document, 'Icons');
if (!iconsFrame || !iconsFrame.children) {
throw new Error('Icons frame not found');
}
const iconIds = iconsFrame.children.map((child: any) => child.id);
const { images } = await api.getImages({
file_key: fileKey,
queryParams: {
ids: iconIds.join(','),
format: 'svg',
},
});
const exports: string[] = [];
for (const [nodeId, url] of Object.entries(images)) {
if (!url) continue;
const response = await fetch(url);
const svgContent = await response.text();
const node = iconsFrame.children.find((c: any) => c.id === nodeId);
const componentName = toPascalCase(node?.name ?? nodeId);
const component = `
export function ${componentName}Icon(props: React.SVGProps<SVGSVGElement>) {
return (
${svgContent.replace('<svg', '<svg {...props}')}
)
}
`.trim();
await fs.writeFile(`components/icons/${componentName}Icon.tsx`, component);
exports.push(
`export { ${componentName}Icon } from './${componentName}Icon'`,
);
}
await fs.writeFile('components/icons/index.ts', exports.join('\n'));
}Using SVGR for Component Generation
For more robust SVG-to-React conversion, use @svgr/core:
import { transform } from '@svgr/core';
async function svgToReactComponent(
svgContent: string,
componentName: string,
): Promise<string> {
const code = await transform(svgContent, {
typescript: true,
plugins: ['@svgr/plugin-svgo', '@svgr/plugin-jsx'],
svgoConfig: {
plugins: [
{ name: 'removeViewBox', active: false },
{ name: 'removeDimensions', active: true },
],
},
});
return code.replace('function SvgComponent', `function ${componentName}`);
}Usage
import { HomeIcon, UserIcon, SettingsIcon } from '@/components/icons';
export function Navigation() {
return (
<nav>
<HomeIcon width={24} height={24} />
<UserIcon width={24} height={24} />
<SettingsIcon width={24} height={24} />
</nav>
);
}Image Export Formats
The GET /v1/images/:key endpoint supports multiple formats:
const svgExport = await api.getImages({
file_key: fileKey,
queryParams: { ids: nodeIds.join(','), format: 'svg' },
});
const pngExport = await api.getImages({
file_key: fileKey,
queryParams: { ids: nodeIds.join(','), format: 'png', scale: 2 },
});
const pdfExport = await api.getImages({
file_key: fileKey,
queryParams: { ids: nodeIds.join(','), format: 'pdf' },
});Image URLs returned by this endpoint expire after 14 days. Do not cache URLs long-term.
Tree Traversal Utilities
function findNode(node: any, name: string): any {
if (node.name === name) return node;
if (node.children) {
for (const child of node.children) {
const found = findNode(child, name);
if (found) return found;
}
}
return null;
}
function toPascalCase(str: string): string {
return str
.split(/[-_\s]+/)
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join('');
}
function normalizeFileName(name: string): string {
return name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
}CI Automation
GitHub Actions Workflow
name: Sync Figma Design Tokens
on:
schedule:
- cron: '0 9 * * *'
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 20
- run: npm install
- name: Sync design tokens
env:
FIGMA_ACCESS_TOKEN: ${{ secrets.FIGMA_ACCESS_TOKEN }}
run: npm run sync:design-tokens
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
title: 'chore: sync design tokens from Figma'
body: 'Automated sync of design tokens from Figma'
branch: 'figma/sync-tokens'
commit-message: 'chore: sync design tokens'Package Scripts
{
"scripts": {
"sync:design-tokens": "tsx scripts/sync-design-tokens.ts",
"export:icons": "tsx scripts/export-icons.ts",
"generate:icons": "tsx scripts/generate-icon-components.ts",
"figma:sync-all": "npm run sync:design-tokens && npm run generate:icons"
}
}Version Checking
Detect Figma file updates before syncing to avoid unnecessary work:
import { Api } from 'figma-api';
import fs from 'fs/promises';
async function checkForUpdates() {
const api = new Api({
personalAccessToken: process.env.FIGMA_ACCESS_TOKEN,
});
const fileKey = 'YOUR_FIGMA_FILE_KEY';
const file = await api.getFile({ file_key: fileKey });
const currentVersion = file.version;
let previousVersion: string | null = null;
try {
previousVersion = await fs.readFile('.figma-version', 'utf-8');
} catch {
// First run
}
if (currentVersion !== previousVersion) {
console.log(`Figma file updated: ${previousVersion} -> ${currentVersion}`);
await syncDesignTokens();
await fs.writeFile('.figma-version', currentVersion);
} else {
console.log('No updates detected');
}
}Webhook-Triggered Sync
Use Figma webhooks for real-time sync instead of polling. Register a webhook for FILE_UPDATE events:
import { Api } from 'figma-api';
async function registerWebhook() {
const api = new Api({
personalAccessToken: process.env.FIGMA_ACCESS_TOKEN,
});
const webhook = await api.postWebhook({
event_type: 'FILE_UPDATE',
team_id: 'YOUR_TEAM_ID',
endpoint: 'https://your-server.com/api/figma-webhook',
passcode: process.env.FIGMA_WEBHOOK_PASSCODE!,
});
console.log('Webhook registered:', webhook.id);
}Then trigger a GitHub Actions workflow via repository dispatch from your webhook handler.
Workflow with Version Gate
Combine version checking with the GitHub Actions workflow to skip unnecessary PRs:
name: Sync Figma (with version check)
on:
schedule:
- cron: '0 9 * * *'
workflow_dispatch:
jobs:
check:
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.version.outputs.changed }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 20
- run: npm install
- name: Check Figma version
id: version
env:
FIGMA_ACCESS_TOKEN: ${{ secrets.FIGMA_ACCESS_TOKEN }}
run: npm run figma:check-version
sync:
needs: check
if: needs.check.outputs.changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 20
- run: npm install
- name: Sync tokens
env:
FIGMA_ACCESS_TOKEN: ${{ secrets.FIGMA_ACCESS_TOKEN }}
run: npm run figma:sync-all
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
title: 'chore: sync design tokens from Figma'
branch: 'figma/sync-tokens'
commit-message: 'chore: sync design tokens'Component Generation
Extract Component Structure
Use GET /v1/files/:key/components and GET /v1/files/:key/component_sets:
import { Api } from 'figma-api';
async function extractComponents() {
const api = new Api({
personalAccessToken: process.env.FIGMA_ACCESS_TOKEN,
});
const fileKey = 'YOUR_FIGMA_FILE_KEY';
const { meta } = await api.getFileComponents({ file_key: fileKey });
for (const component of meta.components) {
console.log(`${component.name} -- Key: ${component.key}`);
console.log(` Description: ${component.description}`);
}
const sets = await api.getFileComponentSets({ file_key: fileKey });
for (const set of sets.meta.component_sets) {
console.log(`Set: ${set.name}`);
}
}Generate Component from Figma Node
Fetch the component node, extract styles, and generate a React component:
import { Api } from 'figma-api';
import fs from 'fs/promises';
async function generateButtonComponent() {
const api = new Api({
personalAccessToken: process.env.FIGMA_ACCESS_TOKEN,
});
const fileKey = 'YOUR_FIGMA_FILE_KEY';
const { meta } = await api.getFileComponents({ file_key: fileKey });
const buttonMeta = meta.components.find((c) =>
c.name.toLowerCase().includes('button'),
);
if (!buttonMeta) {
throw new Error('Button component not found');
}
const file = await api.getFile({ file_key: fileKey });
const buttonNode = findNodeById(file.document, buttonMeta.node_id);
if (!buttonNode) {
throw new Error('Button node not found');
}
const styles = extractStyles(buttonNode);
const component = `
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'ghost'
size?: 'sm' | 'md' | 'lg'
children: React.ReactNode
onClick?: () => void
}
export function Button({
variant = 'primary',
size = 'md',
children,
onClick
}: ButtonProps) {
const baseStyles = {
fontFamily: '${styles.fontFamily}',
fontWeight: ${styles.fontWeight},
fontSize: '${styles.fontSize}px',
padding: '${styles.padding}',
borderRadius: '${styles.borderRadius}px',
border: 'none',
cursor: 'pointer',
transition: 'all 0.2s'
}
const variantStyles = {
primary: {
backgroundColor: '${styles.backgroundColor}',
color: '${styles.color}'
},
secondary: {
backgroundColor: 'transparent',
color: '${styles.backgroundColor}',
border: '2px solid ${styles.backgroundColor}'
},
ghost: {
backgroundColor: 'transparent',
color: '${styles.color}'
}
}
return (
<button
style={{ ...baseStyles, ...variantStyles[variant] }}
onClick={onClick}
>
{children}
</button>
)
}
`.trim();
await fs.writeFile('components/Button.tsx', component);
}Style Extraction
Parse visual properties from Figma node data:
function extractStyles(node: any) {
return {
fontFamily: node.style?.fontFamily || 'Inter',
fontWeight: node.style?.fontWeight || 600,
fontSize: node.style?.fontSize || 16,
padding: '12px 24px',
borderRadius: node.cornerRadius || 8,
backgroundColor: rgbToHex(
node.fills?.[0]?.color || { r: 0, g: 0.4, b: 0.8 },
),
color: '#ffffff',
};
}
function rgbToHex(color: { r: number; g: number; b: number }): string {
const r = Math.round(color.r * 255)
.toString(16)
.padStart(2, '0');
const g = Math.round(color.g * 255)
.toString(16)
.padStart(2, '0');
const b = Math.round(color.b * 255)
.toString(16)
.padStart(2, '0');
return `#${r}${g}${b}`;
}
function findNodeById(node: any, id: string): any {
if (node.id === id) return node;
if (node.children) {
for (const child of node.children) {
const found = findNodeById(child, id);
if (found) return found;
}
}
return null;
}Component Sync Pattern
Keep components in sync with Figma as designs evolve:
async function syncComponent(componentName: string) {
const api = new Api({
personalAccessToken: process.env.FIGMA_ACCESS_TOKEN,
});
const fileKey = 'YOUR_FIGMA_FILE_KEY';
const { meta } = await api.getFileComponents({ file_key: fileKey });
const component = meta.components.find((c) => c.name === componentName);
if (!component) {
throw new Error(`Component not found: ${componentName}`);
}
const file = await api.getFile({ file_key: fileKey });
const node = findNodeById(file.document, component.node_id);
if (!node) {
throw new Error(`Node not found for: ${componentName}`);
}
const styles = extractStyles(node);
const code = generateComponentCode(componentName, styles);
await fs.writeFile(`components/${componentName}.tsx`, code);
}Fetching Specific Nodes
For large files, fetch only the nodes you need instead of the entire file:
async function getComponentNode(fileKey: string, nodeId: string) {
const api = new Api({
personalAccessToken: process.env.FIGMA_ACCESS_TOKEN,
});
const { nodes } = await api.getFileNodes({
file_key: fileKey,
queryParams: { ids: nodeId },
});
return nodes[nodeId]?.document;
}Design Tokens
Design tokens are design decisions (colors, typography, spacing) stored as code -- single source of truth, consistent across platforms, type-safe.
Extract Tokens from Figma Styles
There is no built-in "extract tokens" endpoint. Parse styles from the file response:
import { Api } from 'figma-api';
import fs from 'fs/promises';
async function extractTokensFromStyles() {
const api = new Api({
personalAccessToken: process.env.FIGMA_ACCESS_TOKEN,
});
const fileKey = 'YOUR_FIGMA_FILE_KEY';
const file = await api.getFile({ file_key: fileKey });
const styles = file.styles ?? {};
const colors: Array<{ name: string; hex: string }> = [];
const typography: Array<{
name: string;
family: string;
size: number;
weight: number;
}> = [];
for (const [nodeId, style] of Object.entries(styles)) {
if (style.styleType === 'FILL') {
const node = findNodeById(file.document, nodeId);
if (node?.fills?.[0]?.color) {
const c = node.fills[0].color;
colors.push({
name: normalizeTokenName(style.name),
hex: rgbToHex(c.r, c.g, c.b),
});
}
}
if (style.styleType === 'TEXT') {
const node = findNodeById(file.document, nodeId);
if (node?.style) {
typography.push({
name: normalizeTokenName(style.name),
family: node.style.fontFamily,
size: node.style.fontSize,
weight: node.style.fontWeight,
});
}
}
}
return { colors, typography };
}Extract Tokens from Figma Variables
Figma Variables provide a more structured source for tokens:
async function extractTokensFromVariables() {
const api = new Api({
personalAccessToken: process.env.FIGMA_ACCESS_TOKEN,
});
const fileKey = 'YOUR_FIGMA_FILE_KEY';
const { meta } = await api.getLocalVariables({ file_key: fileKey });
const variables = Object.values(meta.variables);
const collections = meta.variableCollections;
const tokens: Array<{ name: string; value: unknown; collection: string }> =
[];
for (const variable of variables) {
const collection = collections[variable.variableCollectionId];
const modeId = Object.keys(variable.valuesByMode)[0];
const value = variable.valuesByMode[modeId];
tokens.push({
name: normalizeTokenName(variable.name),
value,
collection: collection.name,
});
}
return tokens;
}Generate CSS Variables
function generateCSS(
colors: Array<{ name: string; hex: string }>,
typography: Array<{
name: string;
family: string;
size: number;
weight: number;
}>,
): string {
const lines = [':root {'];
lines.push(' /* Colors */');
for (const color of colors) {
lines.push(` --color-${color.name}: ${color.hex};`);
}
lines.push('');
lines.push(' /* Typography */');
for (const t of typography) {
lines.push(` --font-${t.name}-family: ${t.family};`);
lines.push(` --font-${t.name}-size: ${t.size}px;`);
lines.push(` --font-${t.name}-weight: ${t.weight};`);
}
lines.push('}');
return lines.join('\n');
}CSS Variables Output
:root {
/* Colors */
--color-primary: #0066cc;
--color-secondary: #10b981;
--color-neutral-100: #f9fafb;
--color-neutral-900: #111827;
/* Typography */
--font-heading-family: Inter;
--font-heading-size: 48px;
--font-heading-weight: 700;
/* Spacing */
--space-4: 16px;
--space-8: 32px;
}Using Tokens in Components
export function Button({ children }: { children: React.ReactNode }) {
return (
<button
style={{
backgroundColor: 'var(--color-primary)',
color: 'white',
padding: 'var(--space-4)',
fontFamily: 'var(--font-heading-family)',
fontWeight: 'var(--font-heading-weight)',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
}}
>
{children}
</button>
);
}Generate TypeScript Types
Create type-safe token references:
function generateTokenTypes(
colors: Array<{ name: string }>,
spacing: Array<{ name: string }>,
): string {
return `
export type ColorToken =
${colors.map((c) => `| '${c.name}'`).join('\n ')}
export type SpacingToken =
${spacing.map((s) => `| '${s.name}'`).join('\n ')}
`.trim();
}Usage:
import { type ColorToken } from '@/types/tokens';
interface ButtonProps {
color: ColorToken;
}Style Dictionary Integration
Use style-dictionary to transform tokens across platforms:
import StyleDictionary from 'style-dictionary';
const sd = new StyleDictionary({
source: ['src/styles/design-tokens.json'],
platforms: {
css: {
transformGroup: 'css',
buildPath: 'src/styles/',
files: [{ destination: 'variables.css', format: 'css/variables' }],
},
},
});
await sd.buildAllPlatforms();Utilities
function normalizeTokenName(name: string): string {
return name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
}
function rgbToHex(r: number, g: number, b: number): string {
const toHex = (v: number) =>
Math.round(v * 255)
.toString(16)
.padStart(2, '0');
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
function findNodeById(node: any, id: string): any {
if (node.id === id) return node;
if (node.children) {
for (const child of node.children) {
const found = findNodeById(child, id);
if (found) return found;
}
}
return null;
}Setup and Authentication
Access Token
1. Go to Figma Settings -> Personal access tokens 2. Generate a new token with appropriate scopes (file_content:read for most use cases) 3. Store in environment:
FIGMA_ACCESS_TOKEN=figd_...Client Initialization with figma-api
The figma-api package provides a typed client wrapping the Figma REST API:
import { Api } from 'figma-api';
const api = new Api({
personalAccessToken: process.env.FIGMA_ACCESS_TOKEN,
});
const file = await api.getFile({ file_key: 'abc123xyz' });
console.log('Connected! File:', file.name);Direct REST API Usage
For projects that prefer no dependencies, call the REST API directly:
const FIGMA_BASE = 'https://api.figma.com/v1';
async function figmaGet(path: string) {
const res = await fetch(`${FIGMA_BASE}${path}`, {
headers: {
'X-Figma-Token': process.env.FIGMA_ACCESS_TOKEN!,
},
});
if (!res.ok) {
throw new Error(`Figma API ${res.status}: ${await res.text()}`);
}
return res.json();
}
const file = await figmaGet('/files/YOUR_FILE_KEY');Dependencies
npm install figma-apiFor TypeScript types only (no runtime client):
npm install --save-dev @figma/rest-api-specType usage:
import {
type GetFileResponse,
type GetLocalVariablesResponse,
type GetLocalVariablesPathParams,
type PostVariablesRequestBody,
} from '@figma/rest-api-spec';Figma File Organization
Structure Figma files for automated extraction:
Design System File
├── Cover (description)
├── Colors (all color styles)
├── Typography (all text styles)
├── Spacing (spacing guide)
├── Components
│ ├── Buttons
│ ├── Forms
│ └── Cards
└── Icons (all icons in one frame)Naming Conventions
Use / hierarchy for automated parsing:
Colors: Primary/500, Secondary/500, Neutral/100
Typography: Heading/Large, Body/Regular, Body/Small
Components: Button/Primary, Button/Secondary, Card/DefaultFigma Variables
Figma supports native variables for colors, spacing, border radius, and typography sizes. Extract these via the Variables API endpoints:
const localVars = await api.getLocalVariables({ file_key: 'YOUR_FILE_KEY' });
const publishedVars = await api.getPublishedVariables({
file_key: 'YOUR_FILE_KEY',
});Troubleshooting
Token Names Don't Match
Figma style names contain spaces, slashes, and special characters. Normalize before using as CSS variable names:
function normalizeTokenName(name: string): string {
return name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
}Example: Primary/500 becomes primary-500, Heading/Large becomes heading-large.
Colors Look Different
Figma uses 0-1 RGB values, not 0-255. Multiply and round before hex conversion:
function rgbToHex(color: { r: number; g: number; b: number }): string {
const r = Math.round(color.r * 255);
const g = Math.round(color.g * 255);
const b = Math.round(color.b * 255);
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
}For colors with alpha, handle the a property separately:
function rgbaToHex(color: {
r: number;
g: number;
b: number;
a: number;
}): string {
const hex = rgbToHex(color);
const alpha = Math.round(color.a * 255)
.toString(16)
.padStart(2, '0');
return color.a < 1 ? `${hex}${alpha}` : hex;
}API Rate Limiting
Cache Figma API responses to avoid hitting rate limits:
const cache = new Map<string, { data: any; timestamp: number }>();
const CACHE_TTL = 60_000;
async function getCachedFile(api: any, fileKey: string) {
const cached = cache.get(fileKey);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}
const file = await api.getFile({ file_key: fileKey });
cache.set(fileKey, { data: file, timestamp: Date.now() });
return file;
}Use a 60-second TTL for development. For CI, caching is less important since runs are infrequent.
Expired Image URLs
Image URLs from GET /v1/images/:key expire after 14 days. Do not persist these URLs in databases or config files. Always re-fetch before use:
async function getIconUrl(api: any, fileKey: string, nodeId: string) {
const { images } = await api.getImages({
file_key: fileKey,
queryParams: {
ids: nodeId,
format: 'svg',
},
});
return images[nodeId];
}Large File Responses
For large Figma files, fetching the entire document tree is slow and memory-intensive. Use node-specific endpoints instead:
const { nodes } = await api.getFileNodes({
file_key: fileKey,
queryParams: {
ids: '1:23,4:56',
depth: 2,
},
});The depth parameter limits how deep into the tree the response goes, reducing payload size.
Authentication Errors
Common token issues:
| Error | Cause | Fix |
|---|---|---|
| 403 Forbidden | Token lacks required scope | Regenerate token with file_content:read |
| 404 Not Found | File key is wrong or token lacks access | Verify file key from the Figma URL |
| 429 Too Many Requests | Rate limit exceeded | Add caching and request throttling |
Token starts with fig | Using old token format | Regenerate; current tokens start with figd |