Extract source code, design tokens, and assets from Figma Make (.make) files into runnable React applications
Scanned 9/8/2026
Install to Claude Code
npx -y skills add Aradotso/design-skills --skill figma-make-extractor --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Figma Make Extractor?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/aradotso-figma-make-extractor)More formats (shields.io, HTML) on the badges page.
---
name: figma-make-extractor
description: Extract source code, design tokens, and assets from Figma Make (.make) files into runnable React applications
triggers:
- extract code from figma make file
- decode figma make canvas
- get source code from make file
- extract design tokens from figma
- convert figma make to react app
- parse figma make binary format
- extract assets from make file
- reverse engineer figma make file
---
# Figma Make Extractor
> Skill by [ara.so](https://ara.so) — Design Skills collection.
Extract source code, design tokens, and assets from Figma Make (.make) files. Figma Make is an AI-powered prototyping tool that generates complete React applications. Unlike traditional Figma Design files (.fig), Make files contain React/TypeScript source code, design tokens, assets, and AI chat history - but they're not accessible via the Figma API.
This tool reverse-engineers the .make file format to extract all project contents into a runnable React application.
## Installation
```bash
git clone https://github.com/albertsikkema/figma-make-extractor.git
cd figma-make-extractor/make_extraction
```
No global installation needed - the `run-all.sh` script handles dependency installation automatically.
## Quick Start
```bash
cd make_extraction
./run-all.sh ../YourFile.make
cd output/react_app
npm install --legacy-peer-deps
npm run dev
```
This extracts everything and creates a ready-to-run React app at `output/react_app/`.
## File Structure
A `.make` file is a ZIP archive containing:
```
YourFile.make (ZIP)
├── canvas.fig # Binary file with source code & design data
├── meta.json # Project metadata
├── ai_chat.json # AI conversation history
├── thumbnail.png # Preview image
├── images/ # Image assets
│ ├── [hash1]
│ └── [hash2]
└── blob_store/ # Additional binary data
```
The `canvas.fig` file uses a custom binary format:
- Header: `fig-makee` magic bytes + padding (12 bytes)
- Chunk 1: Deflate-compressed Kiwi schema (4 bytes size + data)
- Chunk 2: Zstandard-compressed message data (4 bytes size + data)
## Key Scripts
All scripts are in the `make_extraction/` directory:
### Master Script
```bash
# Extract everything and create React app
./run-all.sh <path-to-make-file>
# Clean up all generated files
./cleanup.sh
```
### Individual Scripts
```bash
# 1. Decode canvas.fig binary to JSON
node 01-decode-canvas.js
# 2. Extract source code files
node 02-extract-source-code.js
# 3. Extract design tokens (colors, fonts, CSS vars)
node 03-extract-design-tokens.js
# 4. Create runnable React app
node 04-create-react-app.js
```
## Programmatic Usage
### Decoding canvas.fig
```javascript
const fs = require('fs');
const pako = require('pako');
const { decompress } = require('fzstd');
const kiwi = require('kiwi-schema');
// Read binary file
const buffer = fs.readFileSync('output/extracted/canvas.fig');
// Skip 12-byte header (fig-makee + padding)
let offset = 12;
// Read Chunk 1 (schema) - deflate compressed
const chunk1Size = buffer.readUInt32LE(offset);
offset += 4;
const chunk1Data = buffer.slice(offset, offset + chunk1Size);
offset += chunk1Size;
const schemaBytes = pako.inflate(chunk1Data);
// Read Chunk 2 (data) - zstandard compressed
const chunk2Size = buffer.readUInt32LE(offset);
offset += 4;
const chunk2Data = buffer.slice(offset, offset + chunk2Size);
const messageBytes = decompress(chunk2Data);
// Decode using Kiwi schema
const schema = kiwi.compileSchema(schemaBytes);
const decoded = schema.decodeMessage(messageBytes);
// Handle BigInt in JSON
const json = JSON.stringify(decoded, (key, value) =>
typeof value === 'bigint' ? value.toString() : value
, 2);
fs.writeFileSync('decoded-message.json', json);
```
### Extracting Source Files
```javascript
const fs = require('fs');
const decoded = JSON.parse(fs.readFileSync('decoded-message.json', 'utf8'));
const outputDir = 'source_files';
fs.mkdirSync(outputDir, { recursive: true });
let fileCount = 0;
function traverse(node) {
if (!node) return;
// Extract CODE_FILE nodes with source code
if (node.type === 'CODE_FILE' && node.sourceCode) {
const fileName = node.name || `file_${fileCount++}`;
const filePath = `${outputDir}/${fileName}`;
fs.writeFileSync(filePath, node.sourceCode, 'utf8');
console.log(`Extracted: ${fileName}`);
}
// Traverse children
if (node.children) {
node.children.forEach(child => traverse(child));
}
}
traverse(decoded);
```
### Extracting Design Tokens
```javascript
const fs = require('fs');
const path = require('path');
const sourceDir = 'source_files';
const tokens = {
colors: { hex: new Set(), rgba: new Set(), hsl: new Set() },
fonts: new Set(),
cssVariables: new Set()
};
// Regex patterns
const hexPattern = /#[0-9A-Fa-f]{6}(?:[0-9A-Fa-f]{2})?/g;
const rgbaPattern = /rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+(?:\s*,\s*[\d.]+)?\s*\)/g;
const hslPattern = /hsla?\(\s*\d+\s*,\s*\d+%\s*,\s*\d+%(?:\s*,\s*[\d.]+)?\s*\)/g;
const fontPattern = /font-family:\s*["']([^"']+)["']/g;
const cssVarPattern = /--[\w-]+/g;
// Scan all source files
fs.readdirSync(sourceDir).forEach(file => {
const content = fs.readFileSync(path.join(sourceDir, file), 'utf8');
content.match(hexPattern)?.forEach(c => tokens.colors.hex.add(c));
content.match(rgbaPattern)?.forEach(c => tokens.colors.rgba.add(c));
content.match(hslPattern)?.forEach(c => tokens.colors.hsl.add(c));
let fontMatch;
while ((fontMatch = fontPattern.exec(content)) !== null) {
tokens.fonts.add(fontMatch[1]);
}
content.match(cssVarPattern)?.forEach(v => tokens.cssVariables.add(v));
});
// Convert Sets to Arrays
const result = {
colors: {
hex: Array.from(tokens.colors.hex),
rgba: Array.from(tokens.colors.rgba),
hsl: Array.from(tokens.colors.hsl)
},
fonts: Array.from(tokens.fonts),
cssVariables: Array.from(tokens.cssVariables)
};
fs.writeFileSync('design-tokens.json', JSON.stringify(result, null, 2));
```
### Creating React App Structure
```javascript
const fs = require('fs');
const path = require('path');
const sourceDir = 'source_files';
const appDir = 'react_app';
const srcDir = path.join(appDir, 'src');
// Create directory structure
fs.mkdirSync(path.join(srcDir, 'components/ui'), { recursive: true });
fs.mkdirSync(path.join(srcDir, 'hooks'), { recursive: true });
fs.mkdirSync(path.join(srcDir, 'lib'), { recursive: true });
fs.mkdirSync(path.join(srcDir, 'data'), { recursive: true });
fs.mkdirSync(path.join(srcDir, 'styles'), { recursive: true });
fs.mkdirSync(path.join(appDir, 'public'), { recursive: true });
// Organize files by type
fs.readdirSync(sourceDir).forEach(file => {
let targetDir = srcDir;
const content = fs.readFileSync(path.join(sourceDir, file), 'utf8');
// Categorize by content patterns
if (file.startsWith('use-') && file.endsWith('.ts')) {
targetDir = path.join(srcDir, 'hooks');
} else if (file === 'utils.ts' || file === 'cn.ts') {
targetDir = path.join(srcDir, 'lib');
} else if (content.includes('@radix-ui') || content.includes('shadcn')) {
targetDir = path.join(srcDir, 'components/ui');
} else if (file.endsWith('.tsx')) {
targetDir = path.join(srcDir, 'components');
} else if (file.endsWith('.css')) {
targetDir = path.join(srcDir, 'styles');
}
// Fix import paths
let fixedContent = content
.replace(/@radix-ui\/[\w-]+@[\d.]+/g, match => match.split('@')[0] + '@' + match.split('@')[1])
.replace(/from\s+['"]\.\.\/[\w-]+@[\d.]+\//g, 'from "@/')
.replace(/from\s+['"]figma:asset:([^'"]+)['"]/g, (match, hash) => {
return `from "/images/${hash}.png"`;
});
fs.writeFileSync(path.join(targetDir, file), fixedContent, 'utf8');
});
```
### Creating package.json
```javascript
const packageJson = {
"name": "figma-make-app",
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"framer-motion": "^11.0.0",
"lucide-react": "latest"
},
"devDependencies": {
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.4",
"typescript": "^5.6.2",
"vite": "^6.0.1",
"@tailwindcss/postcss": "^4.0.0"
}
};
fs.writeFileSync(
path.join(appDir, 'package.json'),
JSON.stringify(packageJson, null, 2)
);
```
## Common Patterns
### Verifying .make File Format
```bash
# Check if file is ZIP
xxd -l 4 YourFile.make
# Should show: 504b 0304 (PK..)
# Check canvas.fig header
unzip -q YourFile.make
xxd -l 20 canvas.fig
# Should show: 6669 672d 6d61 6b65 65 (fig-makee)
```
### Converting Colors
```javascript
// RGBA float (0-1) to hex
function rgbaToHex(color) {
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')}`.toUpperCase();
}
// Example usage
const figmaColor = { r: 0.11764705926179886, g: 0.11764705926179886, b: 0.11764705926179886, a: 1 };
console.log(rgbaToHex(figmaColor)); // #1E1E1E
```
### Handling Image Assets
```javascript
const fs = require('fs');
const path = require('path');
// Copy images from extracted .make to React app public folder
const imagesSourceDir = 'output/extracted/images';
const imagesTargetDir = 'output/react_app/public/images';
fs.mkdirSync(imagesTargetDir, { recursive: true });
fs.readdirSync(imagesSourceDir).forEach(hash => {
const sourceFile = path.join(imagesSourceDir, hash);
const targetFile = path.join(imagesTargetDir, `${hash}.png`);
fs.copyFileSync(sourceFile, targetFile);
});
```
## Output Structure
After running all scripts, the `output/` directory contains:
```
output/
├── extracted/ # Unzipped .make contents
│ ├── canvas.fig
│ ├── images/
│ ├── meta.json
│ └── ai_chat.json
├── decoded-message.json # Decoded canvas.fig
├── source_files/ # Raw extracted source files
├── design-tokens.json # Colors, fonts, CSS vars
└── react_app/ # Ready-to-run React app
├── package.json
├── vite.config.ts
├── tsconfig.json
├── index.html
├── public/
│ └── images/
└── src/
├── main.tsx
├── App.tsx
├── components/
│ └── ui/ # shadcn/ui components
├── hooks/
├── lib/
├── data/
└── styles/
```
## Troubleshooting
### "Invalid stored block lengths" error
The data chunk uses **Zstandard** compression, not zlib/deflate. Install the correct package:
```bash
npm install fzstd
```
### BigInt serialization error
Use a custom JSON replacer to handle BigInt values:
```javascript
JSON.stringify(decoded, (key, value) =>
typeof value === 'bigint' ? value.toString() : value
, 2);
```
### Empty source code extraction
Verify the node type and property:
```javascript
if (node.type === 'CODE_FILE' && node.sourceCode) {
// Extract here
}
```
Not all nodes have source code - only `CODE_FILE` nodes with the `sourceCode` property.
### Import path errors in React app
The `04-create-react-app.js` script automatically fixes:
- Version numbers in package names: `@radix-ui/react-dialog@1.1.6` → `@radix-ui/react-dialog`
- Figma asset imports: `figma:asset:hash` → `/images/hash.png`
- Relative paths: `../component` → `@/components/component`
If imports still fail, manually check the generated `vite.config.ts` has the alias:
```typescript
resolve: {
alias: {
'@': '/src'
}
}
```
### npm install fails with peer dependency conflicts
Use the `--legacy-peer-deps` flag:
```bash
npm install --legacy-peer-deps
```
This is common with React 18 and older package versions.
### Tailwind CSS PostCSS error
The generated app uses Tailwind v4 with `@tailwindcss/postcss`. Ensure `postcss.config.js` exists:
```javascript
export default {
plugins: {
'@tailwindcss/postcss': {}
}
};
```
### Animations not working (React StrictMode)
The generated `main.tsx` omits React.StrictMode to avoid timer/animation issues with Framer Motion. If you need StrictMode, wrap the app manually but expect animation glitches.
## Node Types Reference
When traversing the decoded message, you'll encounter these node types:
| Type | Description | Has sourceCode |
|------|-------------|----------------|
| `DOCUMENT` | Root document node | No |
| `CANVAS` | Page/canvas container | No |
| `CODE_LIBRARY` | Code library container | No |
| `CODE_FILE` | Source code file (`.tsx`, `.ts`, `.css`) | **Yes** |
| `CODE_COMPONENT` | Exported React component definition | No |
| `CODE_INSTANCE` | Instance of a component | No |
| `FRAME` | Visual frame (preview) | No |
| `RESPONSIVE_SET` | Responsive breakpoint container | No |
Only extract source code from `CODE_FILE` nodes.
## API Reference
This is a CLI tool, not a library. All functionality is accessed via scripts:
```bash
# Full extraction pipeline
./run-all.sh <path-to-make-file>
# Individual steps (after running run-all.sh or extracting manually)
node 01-decode-canvas.js
node 02-extract-source-code.js
node 03-extract-design-tokens.js
node 04-create-react-app.js
# Cleanup
./cleanup.sh
```
## Environment Variables
No environment variables required. All paths are relative to the script locations.
## License
MIT License - see the repository for full license text.
## Disclaimer
This tool documents a reverse-engineering process for educational purposes. The extracted code remains subject to Figma's terms of service and any applicable licenses. Use responsibly.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!