| 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 — 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
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
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
./run-all.sh <path-to-make-file>
./cleanup.sh
Individual Scripts
node 01-decode-canvas.js
node 02-extract-source-code.js
node 03-extract-design-tokens.js
node 04-create-react-app.js
Programmatic Usage
Decoding canvas.fig
const fs = require('fs');
const pako = require('pako');
const { decompress } = require('fzstd');
const kiwi = require('kiwi-schema');
const buffer = fs.readFileSync('output/extracted/canvas.fig');
let offset = 12;
const chunk1Size = buffer.readUInt32LE(offset);
offset += 4;
const chunk1Data = buffer.slice(offset, offset + chunk1Size);
offset += chunk1Size;
const schemaBytes = pako.inflate(chunk1Data);
const chunk2Size = buffer.readUInt32LE(offset);
offset += 4;
const chunk2Data = buffer.slice(offset, offset + chunk2Size);
const messageBytes = decompress(chunk2Data);
const schema = kiwi.compileSchema(schemaBytes);
const decoded = schema.decodeMessage(messageBytes);
const json = JSON.stringify(decoded, () =>
value === ? value.() : value
, );
fs.(, json);
Extracting Source Files
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;
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}`);
}
if (node.children) {
node.children.forEach(child => traverse(child));
}
}
traverse(decoded);
Extracting Design Tokens
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()
};
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;
fs.readdirSync(sourceDir).forEach(file => {
const content = fs.readFileSync(path.join(sourceDir, file), 'utf8');
content.match(hexPattern)?.forEach(c => tokens.colors..(c));
content.(rgbaPattern)?.( tokens...(c));
content.(hslPattern)?.( tokens...(c));
fontMatch;
((fontMatch = fontPattern.(content)) !== ) {
tokens..(fontMatch[]);
}
content.(cssVarPattern)?.( tokens..(v));
});
result = {
: {
: .(tokens..),
: .(tokens..),
: .(tokens..)
},
: .(tokens.),
: .(tokens.)
};
fs.(, .(result, , ));
Creating React App Structure
const fs = require('fs');
const path = require('path');
const sourceDir = 'source_files';
const appDir = 'react_app';
const srcDir = path.join(appDir, 'src');
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 });
fs.readdirSync(sourceDir).forEach(file => {
let targetDir = srcDir;
content = fs.(path.(sourceDir, file), );
(file.() && file.()) {
targetDir = path.(srcDir, );
} (file === || file === ) {
targetDir = path.(srcDir, );
} (content.() || content.()) {
targetDir = path.(srcDir, );
} (file.()) {
targetDir = path.(srcDir, );
} (file.()) {
targetDir = path.(srcDir, );
}
fixedContent = content
.(, match.()[] + + match.()[])
.(, )
.(, {
;
});
fs.(path.(targetDir, file), fixedContent, );
});
Creating package.json
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
xxd -l 4 YourFile.make
unzip -q YourFile.make
xxd -l 20 canvas.fig
Converting Colors
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();
}
const figmaColor = { r: 0.11764705926179886, g: 0.11764705926179886, b: 0.11764705926179886, a: 1 };
console.log(rgbaToHex(figmaColor));
Handling Image Assets
const fs = require('fs');
const path = require('path');
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:
npm install fzstd
BigInt serialization error
Use a custom JSON replacer to handle BigInt values:
JSON.stringify(decoded, (key, value) =>
typeof value === 'bigint' ? value.toString() : value
, 2);
Empty source code extraction
Verify the node type and property:
if (node.type === 'CODE_FILE' && node.sourceCode) {
}
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:
resolve: {
alias: {
'@': '/src'
}
}
npm install fails with peer dependency conflicts
Use the --legacy-peer-deps flag:
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:
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:
./run-all.sh <path-to-make-file>
node 01-decode-canvas.js
node 02-extract-source-code.js
node 03-extract-design-tokens.js
node 04-create-react-app.js
./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.