Learn to use the Figma REST API to programmatically read design data, integrate Tokens Studio with GitHub for automated token export, and build a CI pipeline that keeps design tokens in sync with your CSS codebase.
The Figma REST API gives programmatic access to every element in a design file — frames, components, styles, variables, and their properties. Combined with Tokens Studio and Style Dictionary, it enables a fully automated design token pipeline: designers update values in Figma, tokens are committed to GitHub, CSS is regenerated in CI, and developers review a PR with the exact CSS diff.
This lesson covers the API, automation workflows, and how to use them to eliminate manual copy-paste from every design-to-code handoff.
The Figma REST API is documented at developers.figma.com. The key endpoints for developers:
Authentication: personal access token from Figma Settings > Security > Personal access tokens.
1// Basic Figma API client
2const FIGMA_TOKEN = process.env.FIGMA_ACCESS_TOKEN;
3const FILE_KEY = process.env.FIGMA_FILE_KEY;
4
5async function getFigmaFile() {
6 const response = await fetch(
7 `https://api.figma.com/v1/files/${FILE_KEY}`,
8 { headers: { 'X-Figma-Token': FIGMA_TOKEN } }
9 );
10 if (!response.ok) throw new Error(`Figma API error: ${response.status}`);
11 return response.json();
12}
13
14async function getFigmaStyles() {
15 const response = await fetch(
16 `https://api.figma.com/v1/files/${FILE_KEY}/styles`,
17 { headers: { 'X-Figma-Token': FIGMA_TOKEN } }
18 );
19 return response.json();
20}
21
22async function getFigmaVariables() {
23 const response = await fetch(
24 `https://api.figma.com/v1/files/${FILE_KEY}/variables/local`,
25 { headers: { 'X-Figma-Token': FIGMA_TOKEN } }
26 );
27 return response.json();
28}
29
30// Usage
31const file = await getFigmaFile();
32console.log('Pages:', file.document.children.map(p => p.name));The variables/local endpoint returns all Figma Variables (design tokens) in a structured format. Parsing this produces a tokens.json equivalent:
1// scripts/extract-tokens.js
2const FIGMA_TOKEN = process.env.FIGMA_ACCESS_TOKEN;
3const FILE_KEY = process.env.FIGMA_FILE_KEY;
4
5async function extractTokens() {
6 const response = await fetch(
7 `https://api.figma.com/v1/files/${FILE_KEY}/variables/local`,
8 { headers: { 'X-Figma-Token': FIGMA_TOKEN } }
9 );
10 const data = await response.json();
11
12 const tokens = {};
13
14 // Process variable collections
15 for (const collection of Object.values(data.meta.variableCollections)) {
16 const collectionName = collection.name.toLowerCase().replace(/\s+/g, '-');
17 tokens[collectionName] = {};
18
19 for (const variableId of collection.variableIds) {
20 const variable = data.meta.variables[variableId];
21 if (!variable) continue;
22
23 // Get the default mode value
24 const defaultModeId = collection.defaultModeId;
25 const value = variable.valuesByMode[defaultModeId];
26
27 // Build nested token path from variable name
28 // 'color/action/primary' → tokens.color.action.primary
29 const parts = variable.name.split('/');
30 let current = tokens[collectionName];
31 for (let i = 0; i < parts.length - 1; i++) {
32 const key = parts[i].toLowerCase();
33 current[key] = current[key] || {};
34 current = current[key];
35 }
36 const lastKey = parts[parts.length - 1].toLowerCase();
37
38 // Format value based on type
39 let formattedValue;
40 if (variable.resolvedType === 'COLOR') {
41 const { r, g, b, a } = value;
42 if (a === 1) {
43 // Convert normalized RGB to hex
44 const hex = [r, g, b].map(c =>
45 Math.round(c * 255).toString(16).padStart(2, '0')
46 ).join('');
47 formattedValue = `#${hex}`;
48 } else {
49 formattedValue = `rgba(${Math.round(r*255)},${Math.round(g*255)},${Math.round(b*255)},${a})`;
50 }
51 } else if (variable.resolvedType === 'FLOAT') {
52 formattedValue = `${value}px`;
53 } else {
54 formattedValue = value;
55 }
56
57 current[lastKey] = {
58 value: formattedValue,
59 type: variable.resolvedType.toLowerCase()
60 };
61 }
62 }
63
64 return tokens;
65}
66
67// Write tokens.json
68const tokens = await extractTokens();
69require('fs').writeFileSync(
70 'tokens/figma-extracted.json',
71 JSON.stringify(tokens, null, 2)
72);
73console.log('Tokens extracted from Figma API');The complete automation pipeline:
1# .github/workflows/tokens-sync.yml
2name: Design Token Sync
3
4on:
5 push:
6 paths:
7 - 'tokens/**/*.json'
8 branches-ignore:
9 - main # Don't trigger on main directly
10
11jobs:
12 build-tokens:
13 runs-on: ubuntu-latest
14 steps:
15 - name: Checkout
16 uses: actions/checkout@v4
17
18 - name: Setup Node.js
19 uses: actions/setup-node@v4
20 with:
21 node-version: '20'
22 cache: 'npm'
23
24 - name: Install dependencies
25 run: npm ci
26
27 - name: Build CSS tokens
28 run: npx style-dictionary build
29
30 - name: Check for CSS changes
31 id: check_changes
32 run: |
33 if git diff --quiet src/styles/tokens.css; then
34 echo "changed=false" >> $GITHUB_OUTPUT
35 else
36 echo "changed=true" >> $GITHUB_OUTPUT
37 fi
38
39 - name: Create pull request
40 if: steps.check_changes.outputs.changed == 'true'
41 uses: peter-evans/create-pull-request@v5
42 with:
43 token: ${{ secrets.GITHUB_TOKEN }}
44 commit-message: 'chore(tokens): sync design tokens from Figma'
45 title: 'chore(tokens): update design tokens'
46 body: |
47 Automated token sync from Figma design system.
48
49 Review the CSS diff below to understand what changed.
50 After merging, visual regression tests will verify correctness.
51 branch: 'tokens/sync-${{ github.sha }}'
52 labels: 'design-tokens,automated'Style Dictionary config:
1// style-dictionary.config.js
2module.exports = {
3 source: ['tokens/**/*.json'],
4 platforms: {
5 css: {
6 transformGroup: 'css',
7 buildPath: 'src/styles/',
8 files: [{
9 destination: 'tokens.css',
10 format: 'css/variables',
11 selector: ':root',
12 }]
13 },
14 // Also generate a dark mode file
15 'css-dark': {
16 transformGroup: 'css',
17 buildPath: 'src/styles/',
18 files: [{
19 destination: 'tokens-dark.css',
20 format: 'css/variables',
21 selector: '[data-theme="dark"]',
22 filter: token => token.darkValue !== undefined,
23 }]
24 }
25 }
26};A drift detection script reads Figma API values and compares them against the current CSS custom properties, flagging any discrepancies:
1// scripts/detect-drift.js
2const fs = require('fs');
3
4async function detectDrift() {
5 // Load current CSS tokens
6 const cssContent = fs.readFileSync('src/styles/tokens.css', 'utf-8');
7
8 // Parse CSS custom properties
9 const cssTokens = {};
10 const cssMatches = cssContent.matchAll(/--([\w-]+):\s*([^;]+);/g);
11 for (const [, name, value] of cssMatches) {
12 cssTokens[`--${name}`] = value.trim();
13 }
14
15 // Load Figma-extracted tokens
16 const figmaTokens = JSON.parse(
17 fs.readFileSync('tokens/figma-extracted.json', 'utf-8')
18 );
19
20 // Flatten Figma tokens
21 const flat = {};
22 function flatten(obj, prefix = '') {
23 for (const [key, val] of Object.entries(obj)) {
24 if (val && typeof val === 'object' && val.value !== undefined) {
25 flat[prefix + key] = val.value;
26 } else if (val && typeof val === 'object') {
27 flatten(val, `${prefix}${key}-`);
28 }
29 }
30 }
31 flatten(figmaTokens);
32
33 // Compare
34 const drifted = [];
35 for (const [tokenName, figmaValue] of Object.entries(flat)) {
36 const cssVar = `--${tokenName}`;
37 const cssValue = cssTokens[cssVar];
38 if (cssValue && cssValue !== figmaValue) {
39 drifted.push({ token: cssVar, figma: figmaValue, css: cssValue });
40 }
41 }
42
43 if (drifted.length === 0) {
44 console.log('No drift detected — CSS tokens match Figma.');
45 process.exit(0);
46 } else {
47 console.error(`Drift detected in ${drifted.length} token(s):`);
48 for (const { token, figma, css } of drifted) {
49 console.error(` ${token}\n Figma: ${figma}\n CSS: ${css}`);
50 }
51 process.exit(1); // Fail CI
52 }
53}
54
55detectDrift();Add to package.json scripts:
1{
2 "scripts": {
3 "tokens:extract": "node scripts/extract-tokens.js",
4 "tokens:build": "npx style-dictionary build",
5 "tokens:drift": "node scripts/detect-drift.js",
6 "tokens:sync": "npm run tokens:extract && npm run tokens:build && npm run tokens:drift"
7 }
8}A step-by-step demonstration of the full automated token pipeline from Figma API read to generated CSS custom properties.
1// Complete token pipeline demonstration
2// Step 1: Figma API response (variables/local endpoint)
3const figmaApiResponse = {
4 meta: {
5 variableCollections: {
6 'col1': {
7 name: 'Color Primitives',
8 defaultModeId: 'mode1',
9 variableIds: ['var1', 'var2', 'var3']
10 },
11 'col2': {
12 name: 'Color Semantics',
13 defaultModeId: 'mode1',
14 variableIds: ['var4', 'var5']
15 }
16 },
17 variables: {
18 'var1': {
19 name: 'blue/500',
20 resolvedType: 'COLOR',
21 valuesByMode: {
22 'mode1': { r: 0.145, g: 0.388, b: 0.922, a: 1 }
23 }
24 },
25 'var2': {
26 name: 'blue/600',
27 resolvedType: 'COLOR',
28 valuesByMode: {
29 'mode1': { r: 0.114, g: 0.302, b: 0.847, a: 1 }
30 }
31 },
32 'var3': {
33 name: 'gray/900',
34 resolvedType: 'COLOR',
35 valuesByMode: {
36 'mode1': { r: 0.063, g: 0.094, b: 0.153, a: 1 }
37 }
38 },
39 'var4': {
40 name: 'action/primary',
41 resolvedType: 'COLOR',
42 valuesByMode: {
43 'mode1': { r: 0.145, g: 0.388, b: 0.922, a: 1 }
44 }
45 },
46 'var5': {
47 name: 'text/primary',
48 resolvedType: 'COLOR',
49 valuesByMode: {
50 'mode1': { r: 0.063, g: 0.094, b: 0.153, a: 1 }
51 }
52 }
53 }
54 }
55};
56
57// Step 2: Parse into token structure
58function rgbToHex(r, g, b) {
59 return '#' + [r, g, b]
60 .map(c => Math.round(c * 255).toString(16).padStart(2, '0'))
61 .join('');
62}
63
64function parseVariables(data) {
65 const tokens = {};
66 for (const collection of Object.values(data.meta.variableCollections)) {
67 for (const varId of collection.variableIds) {
68 const variable = data.meta.variables[varId];
69 const value = variable.valuesByMode[collection.defaultModeId];
70 const parts = variable.name.split('/');
71 let current = tokens;
72 for (let i = 0; i < parts.length - 1; i++) {
73 current[parts[i]] = current[parts[i]] || {};
74 current = current[parts[i]];
75 }
76 const lastKey = parts[parts.length - 1];
77 if (variable.resolvedType === 'COLOR') {
78 current[lastKey] = { value: rgbToHex(value.r, value.g, value.b), type: 'color' };
79 }
80 }
81 }
82 return tokens;
83}
84
85const parsedTokens = parseVariables(figmaApiResponse);
86console.log('Parsed tokens:', JSON.stringify(parsedTokens, null, 2));
87/*
88{
89 "blue": {
90 "500": { "value": "#2563eb", "type": "color" },
91 "600": { "value": "#1d4ed8", "type": "color" }
92 },
93 "gray": {
94 "900": { "value": "#101827", "type": "color" }
95 },
96 "action": {
97 "primary": { "value": "#2563eb", "type": "color" }
98 },
99 "text": {
100 "primary": { "value": "#101827", "type": "color" }
101 }
102}
103*/
104
105// Step 3: Style Dictionary generates CSS
106// (Simulated output from: npx style-dictionary build)
107const generatedCSS = `
108/* Auto-generated by Style Dictionary — do not edit manually */
109:root {
110 --color-blue-500: #2563eb;
111 --color-blue-600: #1d4ed8;
112 --color-gray-900: #101827;
113 --color-action-primary: #2563eb;
114 --color-text-primary: #101827;
115}
116`;
117
118console.log('Generated CSS:', generatedCSS);
119
120// Step 4: Components reference only semantic tokens
121// No component CSS changes needed — they already use var(--color-action-primary)
122const componentCSS = `
123.btn-primary {
124 background: var(--color-action-primary); /* automatically correct */
125 color: white;
126}
127.page-title {
128 color: var(--color-text-primary); /* automatically correct */
129}
130`;
131console.log('Component CSS (unchanged):', componentCSS);This pipeline demonstrates the zero-drift token architecture. The Figma API returns normalized RGB values (0-1 range) that the extraction script converts to hex. Style Dictionary transforms the JSON structure to CSS custom properties with the -- prefix naming convention. Component CSS files reference only semantic tokens (--color-action-primary, --color-text-primary) — they never need to change when token values update. When a designer changes the brand color in Figma Variables from #2563EB to a new hue, only the generated tokens.css changes; all .btn-primary, .link, and other components that reference --color-action-primary automatically get the new color.
The next lesson covers translating Figma designs to React components — component mapping strategies, CSS Modules integration, and the systematic process for converting a Figma screen to production React.
Your progress is saved in this browser, on this device only.