A frontend developer told me he spent every sprint manually copying hex codes, font sizes, and spacing values from Figma to CSS. 4 hours per sprint, 50 sprints per year = 200 hours wasted. Then he automated it with Figma's API.
What Figma's API Offers for Free
Figma free plan + REST API:
- Read access to all files you have access to
- Design tokens extraction — colors, typography, spacing
- Component data — inspect any component's properties
- Image export — render any frame as PNG, SVG, JPG, PDF
- Comments API — read and post comments
- File version history
- Webhooks for real-time updates
Quick Start
# Get your Personal Access Token from Figma > Settings > Personal Access Tokens
export FIGMA_TOKEN='figd_YOUR_TOKEN'
# Get file data
curl 'https://api.figma.com/v1/files/YOUR_FILE_KEY' \
-H "X-Figma-Token: $FIGMA_TOKEN" | jq '.document.children | length'
Extract Design Tokens
const FIGMA_TOKEN = process.env.FIGMA_TOKEN;
const FILE_KEY = 'YOUR_FILE_KEY';
async function getFileStyles(fileKey) {
const res = await fetch(`https://api.figma.com/v1/files/${fileKey}/styles`, {
headers: { 'X-Figma-Token': FIGMA_TOKEN }
});
const { meta } = await res.json();
return meta.styles;
}
async function extractColors(fileKey) {
const res = await fetch(`https://api.figma.com/v1/files/${fileKey}`, {
headers: { 'X-Figma-Token': FIGMA_TOKEN }
});
const file = await res.json();
const colors = {};
// Walk the document tree to find color styles
function walk(node) {
if (node.fills && node.fills[0]?.type === 'SOLID') {
const { r, g, b, a } = node.fills[0].color;
const hex = `#${[r, g, b].map(v => Math.round(v * 255).toString(16).padStart(2, '0')).join('')}`;
if (node.name.startsWith('color/')) {
colors[node.name] = hex;
}
}
node.children?.forEach(walk);
}
walk(file.document);
return colors;
}
// Generate CSS custom properties
const colors = await extractColors(FILE_KEY);
const css = ':root {\n' +
Object.entries(colors).map(([name, hex]) =>
` --${name.replace('/', '-')}: ${hex};`
).join('\n') +
'\n}';
console.log(css);
// :root {
// --color-primary: #3b82f6;
// --color-secondary: #10b981;
// --color-danger: #ef4444;
// }
Export Images from Figma
async function exportFrames(fileKey, nodeIds, format = 'png', scale = 2) {
const ids = nodeIds.join(',');
const res = await fetch(
`https://api.figma.com/v1/images/${fileKey}?ids=${ids}&format=${format}&scale=${scale}`,
{ headers: { 'X-Figma-Token': FIGMA_TOKEN } }
);
const { images } = await res.json();
// Download each image
for (const [nodeId, url] of Object.entries(images)) {
const imgRes = await fetch(url);
const buffer = await imgRes.arrayBuffer();
require('fs').writeFileSync(`./exports/${nodeId}.${format}`, Buffer.from(buffer));
}
}
// Export specific frames
await exportFrames(FILE_KEY, ['1:23', '4:56'], 'svg');
Component Inspector
async function getComponents(fileKey) {
const res = await fetch(`https://api.figma.com/v1/files/${fileKey}/components`, {
headers: { 'X-Figma-Token': FIGMA_TOKEN }
});
const { meta } = await res.json();
return meta.components.map(c => ({
name: c.name,
description: c.description,
key: c.key,
containingFrame: c.containing_frame?.name
}));
}
Webhooks (Real-Time Updates)
// Register a webhook
const webhook = await fetch('https://api.figma.com/v2/webhooks', {
method: 'POST',
headers: {
'X-Figma-Token': FIGMA_TOKEN,
'Content-Type': 'application/json'
},
body: JSON.stringify({
event_type: 'FILE_UPDATE',
team_id: 'TEAM_ID',
endpoint: 'https://your-server.com/figma-webhook',
passcode: 'your-secret'
})
});
// Handle webhook events
app.post('/figma-webhook', (req, res) => {
if (req.body.passcode !== 'your-secret') return res.sendStatus(403);
console.log(`File ${req.body.file_key} updated by ${req.body.triggered_by.handle}`);
// Re-extract design tokens, rebuild CSS, notify team...
res.sendStatus(200);
});
Automation Ideas
- Design token pipeline — Figma → API → CSS/Tailwind config → deploy
- Screenshot comparison — export frames, compare with production
- Component documentation — auto-generate docs from Figma components
- Asset pipeline — auto-export icons as SVG when file updates
- Design QA — compare implemented UI with Figma designs pixel-by-pixel
Need to scrape design inspiration? Check out my web scraping actors on Apify — collect data from Dribbble, Behance, and more.
Need a design-to-code pipeline? Email me at spinov001@gmail.com.
Top comments (2)
Writing custom node traversal scripts against the Figma REST API gets quite messy once you start trying to resolve complex variable aliases instead of just raw hex values. I shifted over to using a Figma plugin called DS Sync a while back, which handles the exact same token extraction but pushes directly to a GitHub PR as W3C JSON. Are you running your extraction script via a webhook or just triggering it locally when designs change?
Honest answer: this one was an API walkthrough, not something I run on a cron in production, so treat the webhook block as a sketch. But if I were wiring it for real, I'd trigger off the FILE_UPDATE webhook and treat the event purely as a change-signal, not a diff. Figma's webhook tells you the file changed, never which node changed, so you re-walk and re-resolve the whole variable graph on every fire anyway. The alias mess you hit is the actual hard part: variable aliases chain (alias to alias to primitive), so raw-hex extraction works right up until someone introduces an aliased token, and then you need a topological resolve pass or you emit half-resolved values. DS Sync pushing W3C JSON straight to a PR is the piece I'd copy regardless of the trigger, because it puts the token diff in code review, where a human sees "primary went blue to teal" before it ships instead of a webhook silently rebuilding CSS. Local vs webhook matters a lot less than having that PR gate in the middle.