
Sync Android drawable assets from Figma to GitHub
Description
Categories
π§ Engineeringπ€ AI & Machine Learning
Nodes Used
n8n-nodes-base.ifn8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.mergen8n-nodes-base.mergen8n-nodes-base.stickyNoten8n-nodes-base.stickyNoten8n-nodes-base.httpRequest
PriceKostenlos
Views0
Last Updated11/28/2025
workflow.json
{
"id": "Wyw2TmhKVujBRhkZ",
"meta": {
"instanceId": "14e4c77104722ab186539dfea5182e419aecc83d85963fe13f6de862c875ebfa",
"templateCredsSetupCompleted": true
},
"name": "Sync Android drawable assets from Figma to GitHub",
"tags": [],
"nodes": [
{
"id": "b9ed0516-1d34-499b-85ba-7e304129b116",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
660,
-280
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineAll"
},
"typeVersion": 3.2
},
{
"id": "8c08ddf1-e081-425c-bdb1-b46e28f48ebf",
"name": "Download Each Image from the Figma Export URL",
"type": "n8n-nodes-base.httpRequest",
"position": [
1320,
-355
],
"parameters": {
"url": "={{ $json[\"url\"] }}",
"options": {
"response": {
"response": {
"responseFormat": "file"
}
}
}
},
"typeVersion": 4.2
},
{
"id": "139ce28e-570d-443d-a347-bef3089e6e1e",
"name": "get figma Url",
"type": "n8n-nodes-base.httpRequest",
"position": [
880,
-355
],
"parameters": {
"url": "=https://api.figma.com/v1/images/FIGMA_PROJECT_ID?ids={{$json[\"id\"]}}&format=png&scale={{$json[\"scale\"]}}",
"options": {},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-Figma-Token",
"value": "FIGMA_TOKEN"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "383243fc-1bd0-4968-af5e-fb6ba09e80f9",
"name": "Merge1",
"type": "n8n-nodes-base.merge",
"position": [
1560,
-220
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3.2,
"alwaysOutputData": true
},
{
"id": "087cce4c-2102-48f8-bee1-69468fceeec9",
"name": "If",
"type": "n8n-nodes-base.if",
"position": [
1840,
-220
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "4f1fd504-3119-4fe0-bb8f-6af824494d41",
"operator": {
"type": "number",
"operation": "equals"
},
"leftValue": "={{$itemIndex}}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.2
},
{
"id": "258614f5-1704-4972-bbd4-e0bf8f4126d3",
"name": "Get Figma Export URL",
"type": "n8n-nodes-base.httpRequest",
"position": [
220,
-280
],
"parameters": {
"url": "https://api.figma.com/v1/files/FIGMA_PROJECT_ID?ids=2-20347",
"options": {},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-Figma-Token",
"value": "FIGMA_TOKEN"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "417bd211-81b9-4f1c-9cdf-15b01a2d39ab",
"name": "Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"position": [
0,
-280
],
"parameters": {},
"typeVersion": 1
},
{
"id": "03ff18dd-880b-4930-9d61-6e16d5e08078",
"name": "Find Icons & Buttons",
"type": "n8n-nodes-base.code",
"position": [
440,
-380
],
"parameters": {
"jsCode": "function findIconsAndButtons(nodes, parentChain = []) {\n let matches = [];\n\n for (const node of nodes) {\n const fullPath = [...parentChain, node.name].join('/');\n\n // If current node is a match\n if (typeof node.name === 'string' && /icon|button/i.test(node.name)) {\n matches.push({\n id: node.id,\n name: node.name,\n type: node.type,\n fullPath: fullPath,\n exportSettings: node.exportSettings || null,\n });\n }\n\n // Recurse into children if they exist\n if (node.children && Array.isArray(node.children)) {\n matches.push(...findIconsAndButtons(node.children, [...parentChain, node.name]));\n }\n }\n\n return matches;\n}\n\n// Start from the root document children\nconst rootNode = items[0].json.document;\nconst canvas = rootNode.children.find(child => child.type === 'CANVAS');\nconst frame = canvas.children.find(child => child.type === 'FRAME' && /forgot password/i.test(child.name));\n\nif (!frame || !frame.children) {\n return [];\n}\n\n// Recursively collect icons and buttons\nconst results = findIconsAndButtons(frame.children);\n\nreturn results.map(node => ({\n json: node\n}));"
},
"typeVersion": 2,
"alwaysOutputData": true
},
{
"id": "6ea0e89f-aeef-46b0-b26b-22dcace571de",
"name": "Predefine drawable folders",
"type": "n8n-nodes-base.code",
"position": [
440,
-180
],
"parameters": {
"jsCode": "return [\n { json: { folder: 'drawable-mdpi', scale: '1' } },\n { json: { folder: 'drawable-hdpi', scale: '1.5' } },\n { json: { folder: 'drawable-xhdpi', scale: '2' } },\n { json: { folder: 'drawable-xxhdpi', scale: '3' } }\n];"
},
"typeVersion": 2
},
{
"id": "1ac6faec-8395-4f4e-bea1-82344b1a4fd8",
"name": "Filter nullable url of nodes",
"type": "n8n-nodes-base.code",
"position": [
1100,
-360
],
"parameters": {
"jsCode": "// Filter valid images and map to { nodeId, url }\nconst results = [];\n\nfor (const item of items) {\n const images = item.json.images;\n\n for (const nodeId in images) {\n const url = images[nodeId];\n\n if (url) {\n results.push({\n json: {\n nodeId,\n url\n }\n });\n }\n }\n}\n\nreturn results;"
},
"typeVersion": 2
},
{
"id": "053297b8-705d-42b9-9531-16bf91629f93",
"name": "Edit File names",
"type": "n8n-nodes-base.code",
"position": [
1700,
-360
],
"parameters": {
"jsCode": "return items.map(item => {\n const name = item.json.name || \"asset\";\n const folder = item.json.folder || \"drawable-mdpi\";\n \n item.json.path = `app/src/main/res/${folder}/${name.toLowerCase().replace(/[^a-z0-9]/g, \"_\")}.png`;\n item.json.commitMessage = `Add asset: ${name}.png to ${folder}`;\n item.json.branch = \"add-assets-from-figma\";\n item.json.repoOwner = \"REPO_OWNER\";\n item.json.repoName = \"REPO_NAME\";\n\n return item;\n});"
},
"typeVersion": 2
},
{
"id": "1678d236-4bab-4b39-a553-cd67f9cffe1a",
"name": "Prepare Pull Request",
"type": "n8n-nodes-base.httpRequest",
"position": [
2040,
-220
],
"parameters": {
"url": "https://api.github.com/repos/REPO_OWNER/REPO_NAME/pulls",
"method": "POST",
"options": {},
"jsonBody": "{\n \"title\": \"Add drawable assets from Figma\",\n \"head\": \"add-assets-from-figma\",\n \"base\": \"main\",\n \"body\": \"This PR contains Android drawable assets exported from Figma in all resolutions.\"\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer GITHUB_TOKEN"
},
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "4ccbac73-ffba-45a7-a6fe-a77b8021480f",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-40,
-460
],
"parameters": {
"width": 2280,
"height": 440,
"content": "\nSync Android drawable assets from Figma to GitHub via PR (multiβdensity PNG)"
},
"typeVersion": 1
},
{
"id": "09523583-b2db-40d7-97df-798608513226",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-40,
60
],
"parameters": {
"width": 2280,
"height": 760,
"content": "#\nNode Name\nDescription\n\n1 π Execute Flow\nTrigger node to start the automation manually or via webhook/schedule.\n\n2 π¨ Get Figma Export URLs\nFetches Figma export URLs (PNG/SVG) for all components from a specified file or parent node.\n\n3 π§ Find Icons & Buttons\nFilters nodes by name or type to extract only relevant UI components like βIconβ and βButtonβ.\n\n4 π Predefine Drawable Folders\nCreates static Android drawable folder list (mdpi, hdpi, etc.) as JSON for merging later.\n\n5 π Merge Metadata & Folders\nMerges filtered Figma nodes with predefined Android folder structure for each resolution.\n\n6 π Get Figma Image URLs\nCalls Figma export API with IDs from merged metadata to retrieve actual exportable image URLs.\n\n7 π« Filter Empty Image URLs\nRemoves any nodes where export URL is missing/null to avoid failed downloads or commits.\n\n8 π₯ Download Figma Images\nDownloads binary image files from export URLs for all filtered nodes across densities.\n\n9 𧬠Merge Metadata & Images\nMerges previously prepared metadata (drawable folder, file name) with actual downloaded image files.\n\n10 π Edit File Names\nRenames files based on naming conventions (e.g., lowercase, no spaces, append _icon if needed).\n\n11 π If: Pull Request Created?\nPrevents duplicate pull requests by checking if one already exists for the same branch or purpose.\n\n12 π Prepare Pull Request\nCommits all images to GitHub in proper folders and creates a clean Pull Request into the main branch.\n\n"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "da71a6f8-28a7-467f-998e-a4fef0700dc8",
"connections": {
"If": {
"main": [
[
{
"node": "Prepare Pull Request",
"type": "main",
"index": 0
}
]
]
},
"Merge": {
"main": [
[
{
"node": "get figma Url",
"type": "main",
"index": 0
},
{
"node": "Merge1",
"type": "main",
"index": 1
}
]
]
},
"Merge1": {
"main": [
[
{
"node": "Edit File names",
"type": "main",
"index": 0
}
]
]
},
"get figma Url": {
"main": [
[
{
"node": "Filter nullable url of nodes",
"type": "main",
"index": 0
}
]
]
},
"Edit File names": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"Execute workflow": {
"main": [
[
{
"node": "Get Figma Export URL",
"type": "main",
"index": 0
}
]
]
},
"Find Icons & Buttons": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Get Figma Export URL": {
"main": [
[
{
"node": "Find Icons & Buttons",
"type": "main",
"index": 0
},
{
"node": "Predefine drawable folders",
"type": "main",
"index": 0
}
]
]
},
"Predefine drawable folders": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Filter nullable url of nodes": {
"main": [
[
{
"node": "Download Each Image from the Figma Export URL",
"type": "main",
"index": 0
}
]
]
},
"Download Each Image from the Figma Export URL": {
"main": [
[
{
"node": "Merge1",
"type": "main",
"index": 0
}
]
]
}
}
}