Zapier/Make to n8n Migration Kit
Module 23 · ship · advanced
starter.json
In n8n, copy this file and paste it onto the canvas, or use Import from File.
{
"name": "23 · Zapier/Make to n8n Migration Kit (starter)",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "mig/convert",
"responseMode": "responseNode",
"options": {}
},
"id": "m23-convert-api",
"name": "Convert API",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
0
],
"webhookId": "course-23-convert"
},
{
"parameters": {
"httpMethod": "GET",
"path": "mig/mappings",
"responseMode": "responseNode",
"options": {}
},
"id": "m23-mappings-api",
"name": "Mappings API",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
400
],
"webhookId": "course-23-mappings"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "s1",
"name": "app_map",
"value": "={{ {\"webhook\": {\"trigger\": [\"n8n-nodes-base.webhook\",2],\"action\": [\"n8n-nodes-base.httpRequest\",4.2]},\"http\": {\"action\": [\"n8n-nodes-base.httpRequest\",4.2]},\"schedule\": {\"trigger\": [\"n8n-nodes-base.scheduleTrigger\",1.2]},\"gmail\": {\"trigger\": [\"n8n-nodes-base.gmailTrigger\",1.3],\"action\": [\"n8n-nodes-base.gmail\",2.1],\"credential\": \"Gmail\"},\"googlesheets\": {\"trigger\": [\"n8n-nodes-base.googleSheetsTrigger\",1],\"action\": [\"n8n-nodes-base.googleSheets\",4.5],\"credential\": \"Google Sheets\"},\"googledrive\": {\"trigger\": [\"n8n-nodes-base.googleDriveTrigger\",1],\"action\": [\"n8n-nodes-base.googleDrive\",3],\"credential\": \"Google Drive\"},\"googlecalendar\": {\"trigger\": [\"n8n-nodes-base.googleCalendarTrigger\",1],\"action\": [\"n8n-nodes-base.googleCalendar\",1.3],\"credential\": \"Google Calendar\"},\"slack\": {\"trigger\": [\"n8n-nodes-base.slackTrigger\",1],\"action\": [\"n8n-nodes-base.slack\",2.3],\"credential\": \"Slack\"},\"hubspot\": {\"trigger\": [\"n8n-nodes-base.hubspotTrigger\",1],\"action\": [\"n8n-nodes-base.hubspot\",2.1],\"credential\": \"HubSpot\"},\"airtable\": {\"trigger\": [\"n8n-nodes-base.airtableTrigger\",1],\"action\": [\"n8n-nodes-base.airtable\",2.1],\"credential\": \"Airtable\"},\"notion\": {\"trigger\": [\"n8n-nodes-base.notionTrigger\",1],\"action\": [\"n8n-nodes-base.notion\",2.2],\"credential\": \"Notion\"},\"stripe\": {\"trigger\": [\"n8n-nodes-base.stripeTrigger\",1],\"action\": [\"n8n-nodes-base.stripe\",1],\"credential\": \"Stripe\"},\"shopify\": {\"trigger\": [\"n8n-nodes-base.shopifyTrigger\",1],\"action\": [\"n8n-nodes-base.shopify\",1],\"credential\": \"Shopify\"},\"typeform\": {\"trigger\": [\"n8n-nodes-base.typeformTrigger\",1],\"credential\": \"Typeform\"},\"calendly\": {\"trigger\": [\"n8n-nodes-base.calendlyTrigger\",1],\"credential\": \"Calendly\"},\"telegram\": {\"trigger\": [\"n8n-nodes-base.telegramTrigger\",1.1],\"action\": [\"n8n-nodes-base.telegram\",1.2],\"credential\": \"Telegram\"},\"discord\": {\"action\": [\"n8n-nodes-base.discord\",2],\"credential\": \"Discord\"},\"trello\": {\"trigger\": [\"n8n-nodes-base.trelloTrigger\",1],\"action\": [\"n8n-nodes-base.trello\",1],\"credential\": \"Trello\"},\"mailchimp\": {\"trigger\": [\"n8n-nodes-base.mailchimpTrigger\",1],\"action\": [\"n8n-nodes-base.mailchimp\",1],\"credential\": \"Mailchimp\"},\"pipedrive\": {\"trigger\": [\"n8n-nodes-base.pipedriveTrigger\",1],\"action\": [\"n8n-nodes-base.pipedrive\",1],\"credential\": \"Pipedrive\"},\"openai\": {\"action\": [\"@n8n/n8n-nodes-langchain.openAi\",1.8],\"credential\": \"OpenAI\"},\"setvariable\": {\"action\": [\"n8n-nodes-base.set\",3.4]},\"delay\": {\"action\": [\"n8n-nodes-base.wait\",1.1]},\"code\": {\"action\": [\"n8n-nodes-base.code\",2]} } }}",
"type": "object"
},
{
"id": "s2",
"name": "app_aliases",
"value": "={{ {\"gateway\": \"webhook\",\"webhooks\": \"webhook\",\"catchhook\": \"webhook\",\"googleemail\": \"gmail\",\"googlemail\": \"gmail\",\"googlesheet\": \"googlesheets\",\"hubspotcrm\": \"hubspot\",\"openaigpt3\": \"openai\",\"chatgpt\": \"openai\",\"util\": \"setvariable\",\"json\": \"code\",\"formatter\": \"code\",\"regexp\": \"code\",\"javascript\": \"code\",\"sleep\": \"delay\",\"scheduler\": \"schedule\"} }}",
"type": "object"
},
{
"id": "s3",
"name": "hours_mapped",
"value": 0.25,
"type": "number"
},
{
"id": "s4",
"name": "hours_partial",
"value": 0.75,
"type": "number"
},
{
"id": "s5",
"name": "hours_manual",
"value": 2,
"type": "number"
},
{
"id": "s6",
"name": "hours_testing",
"value": 1,
"type": "number"
},
{
"id": "s7",
"name": "hourly_rate",
"value": 75,
"type": "number"
},
{
"id": "s8",
"name": "zapier_cost_per_1000_tasks",
"value": 30,
"type": "number"
},
{
"id": "s9",
"name": "make_cost_per_10000_ops",
"value": 11,
"type": "number"
},
{
"id": "s10",
"name": "n8n_monthly_cost",
"value": 20,
"type": "number"
}
]
},
"includeOtherFields": true,
"options": {}
},
"id": "m23-settings-edit-me",
"name": "Settings (edit me)",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
240,
200
]
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "r1",
"leftValue": "={{ $('Convert API').isExecuted }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "convert"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "r2",
"leftValue": "={{ $('Mappings API').isExecuted }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "mappings"
}
]
},
"options": {}
},
"id": "m23-route",
"name": "Route",
"type": "n8n-nodes-base.switch",
"typeVersion": 3.2,
"position": [
460,
200
]
},
{
"parameters": {
"jsCode": "// Read a Make blueprint or a Zapier export and turn every scenario/zap into the same step tree:\n// { key, label, app, action, kind: trigger|action|router|iterator|aggregator, params, filter, routes }\nconst body = $('Convert API').first().json.body ?? {};\nconst parse = (v) => (typeof v === 'string' ? JSON.parse(v) : v);\nconst appKey = (s) => String(s ?? '').replace(/@.*$/, '').replace(/API$/i, '').replace(/V\\d+$/i, '').toLowerCase().replace(/[^a-z0-9]/g, '');\n\n// --- Make: blueprint.flow[] with module \"app:action\", mapper/parameters, filter on the module, router routes\nfunction makeSteps(flow, isRoot) {\n return (flow ?? []).map((m, i) => {\n const [app, action] = String(m.module ?? '').split(':');\n const kind = m.module === 'builtin:BasicRouter' ? 'router' : m.module === 'builtin:BasicFeeder' ? 'iterator'\n : /Aggregator$/.test(action ?? '') ? 'aggregator' : isRoot && i === 0 ? 'trigger' : 'action';\n return {\n key: String(m.id), label: m.metadata?.designer?.name || m.label || `${app} ${action}`,\n app: appKey(app), app_raw: app, action: action ?? '', kind,\n params: { ...(m.parameters ?? {}), ...(m.mapper ?? {}) },\n filter: m.filter ? { label: m.filter.name ?? 'Filter', groups: (m.filter.conditions ?? []).map((g) => g.map((c) => ({ a: c.a, op: c.o, b: c.b }))) } : null,\n routes: kind === 'router' ? (m.routes ?? []).map((r, j) => ({ label: r.flow?.[0]?.filter?.name || `Route ${j + 1}`, steps: makeSteps(r.flow, false) })) : null,\n };\n });\n}\n\n// --- Zapier: zapfile { zaps: [{ title, nodes: { id: { selected_api, action, type_of, params, parent_id } } }] }\nconst zapOp = { exact: 'text:equal', iexact: 'text:equal', contains: 'text:contain', icontains: 'text:contain', gt: 'number:greater', lt: 'number:less', exists: 'exist', not_exists: 'notexist', is_true: 'boolean:true', is_false: 'boolean:false' };\nfunction zapSteps(nodes) {\n const all = Object.values(nodes ?? {});\n const kids = (id) => all.filter((n) => String(n.parent_id ?? '') === String(id ?? '')).sort((a, b) => Number(a.id) - Number(b.id));\n const filterOf = (n) => ({ label: n.title || 'Filter', groups: (n.params?.filter ?? n.params?.filter_criteria ?? []).map((g) => g.map((c) => ({ a: c.key, op: zapOp[c.match] ?? c.match, b: c.value }))) });\n function chain(start, isRoot) {\n const out = [];\n let pendingFilter = null;\n for (let n = start, i = 0; n; i++) {\n const app = appKey(n.selected_api);\n if (app === 'filter') { pendingFilter = filterOf(n); n = kids(n.id)[0]; continue; }\n const children = kids(n.id);\n const isPaths = /^(branching|paths)/.test(app);\n out.push({\n key: String(n.id), label: n.title || `${app} ${n.action ?? ''}`.trim(), app, app_raw: n.selected_api, action: n.action ?? '',\n kind: isPaths ? 'router' : n.type_of === 'read' && isRoot && i === 0 ? 'trigger' : 'action',\n params: n.params ?? {}, filter: pendingFilter,\n routes: isPaths ? children.map((c) => ({ label: c.title || 'Path', filter: c.params?.filter ? filterOf(c) : null, steps: chain(kids(c.id)[0], false) })) : null,\n });\n pendingFilter = null;\n if (isPaths) break;\n n = children[0];\n }\n return out;\n }\n return chain(kids(null)[0] ?? all.find((n) => !n.parent_id), true);\n}\n\nlet scenarios = [], error = null;\ntry {\n if (body.blueprint || body.blueprints) {\n for (const bp of (body.blueprints ?? [body.blueprint]).map(parse))\n scenarios.push({ name: bp.name || 'Make scenario', source: 'make', steps: makeSteps(bp.flow, true) });\n } else if (body.zapfile || body.zap) {\n const zf = parse(body.zapfile ?? { zaps: [parse(body.zap)] });\n for (const z of zf.zaps ?? []) scenarios.push({ name: z.title || `Zap ${z.id}`, source: 'zapier', steps: zapSteps(z.nodes) });\n } else error = 'send \"blueprint\" (Make export), \"blueprints\" (array) or \"zapfile\" / \"zap\" (Zapier export)';\n} catch (e) { error = `could not read the export: ${e.message}`; }\nif (!error && !scenarios.some((s) => s.steps.length)) error = 'no steps found in the export';\n\nconst runs = body.monthly_runs ?? {};\nreturn [{ json: { ok: !error, error, scenarios: scenarios.map((s) => ({ ...s, monthly_runs: Number(typeof runs === 'number' ? runs : runs[s.name] ?? 1000) })),\n create: body.create === true, dry_run: body.dry_run === true } }];\n"
},
"id": "m23-read-export",
"name": "Read Export",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
700,
0
]
},
{
"parameters": {
"jsCode": "// What the converter knows: apps with trigger/action equivalents and their aliases.\nconst s = $('Settings (edit me)').first().json;\nconst apps = Object.entries(s.app_map).map(([app, m]) => ({ app, trigger: m.trigger?.[0] ?? null, action: m.action?.[0] ?? null, credential: m.credential ?? null,\n aliases: Object.entries(s.app_aliases).filter(([, v]) => v === app).map(([k]) => k) }));\nreturn [{ json: { count: apps.length, apps } }];\n"
},
"id": "m23-list-mappings",
"name": "List Mappings",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
700,
400
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"id": "m23-respond-mappings",
"name": "Respond Mappings",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
920,
400
]
},
{
"parameters": {
"content": "## Your task\nExports are already read into one step tree (try `samples/make-blueprint.json` as `blueprint`), and the mapping table is in Settings.\n1. **Convert to n8n** (Code): per step, look up `app_map` (trigger or action node), rewrite `{{1.email}}` / `{{1__email}}` to `$('Step').item.json.email` (webhooks: `.body.email`), filters → If, routers/paths → Switch (all matching outputs), unknown apps → No Op placeholder; lay nodes out and connect them.\n2. **Build Report**: mapped/partial/manual, credentials, effort hours, old platform cost, checklist, Markdown.\n3. Valid? → respond (400 otherwise). Optional: create the workflows with the **n8n** node (API credential).\n\nTest: `./test/test.sh`",
"height": 260,
"width": 420
},
"id": "starter-task-note",
"name": "Your Task",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
0,
-340
]
}
],
"connections": {
"Convert API": {
"main": [
[
{
"node": "Settings (edit me)",
"type": "main",
"index": 0
}
]
]
},
"Mappings API": {
"main": [
[
{
"node": "Settings (edit me)",
"type": "main",
"index": 0
}
]
]
},
"Settings (edit me)": {
"main": [
[
{
"node": "Route",
"type": "main",
"index": 0
}
]
]
},
"Route": {
"main": [
[
{
"node": "Read Export",
"type": "main",
"index": 0
}
],
[
{
"node": "List Mappings",
"type": "main",
"index": 0
}
]
]
},
"Read Export": {
"main": [
[]
]
},
"List Mappings": {
"main": [
[
{
"node": "Respond Mappings",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"pinData": {}
}