Zapier/Make to n8n Migration Kit
Module 23 · ship · advanced
Solutions 23 · Zapier/Make to n8n Migration Kit
None of these have been run yet.
Exercise 1
New workflow:
- Form Trigger: one File field
export(accept.json), plus a Number fieldmonthly_runs. - Extract From File JSON (binary property
export) → the export as JSON. - HTTP Request POST
http://localhost:5678/webhook/mig/convertwith body{{ { blueprint: $json.data.flow ? $json.data : undefined, zapfile: $json.data.zaps ? $json.data : undefined, monthly_runs: $('Form Trigger').item.json.monthly_runs } }}. - Form Trigger option Respond When: Workflow Finishes with a Form Completion page showing
{{ $json.report_markdown }}(Markdown renders in the completion text).
Exercise 2
In convert.js, add to paramsFor:
case 'n8n-nodes-base.airtable': {
const base = pick('base', 'baseId', 'base_id'); const table = pick('table', 'tableId', 'table_name');
const fields = p.record ?? p.fields ?? {};
return [{ operation: 'create', base: { __rl: true, mode: 'id', value: String(base ?? '').replace(/^=/, '') },
table: { __rl: true, mode: 'id', value: String(table ?? '').replace(/^=/, '') },
columns: { mappingMode: 'defineBelow', value: fields }, options: {} }, Boolean(base && table && Object.keys(fields).length)];
}
tx has already rewritten the field values (the p object), so {{1.email}} arrives as an n8n expression.
Exercise 3
Add a Code node after Build Report (or a proposal: true option):
const s = $('Settings (edit me)').first().json;
const r = $input.first().json;
const savings = r.totals.old_monthly_estimate - s.n8n_monthly_cost;
const cost = r.totals.effort_hours * s.hourly_rate;
const rows = r.scenarios.map((x) => `- ${x.name}: ${x.coverage.percent_ready}% converts automatically, ${x.effort_hours} h, ~$${x.cost.old_monthly_estimate}/month today`);
const text = [
`Moving your ${r.totals.scenarios} automations to n8n`, '', ...rows, '',
`One-time migration: ${r.totals.effort_hours} h ($${cost}).`,
savings > 0 ? `Estimated savings: $${savings.toFixed(0)}/month; pays for itself in ${Math.ceil(cost / savings)} months.` : 'Savings are small at current volume; the main win is flexibility and no per-task pricing as you grow.',
'Includes a one-week parallel run before switching off the old platform.',
].join('\n');
return [{ json: { ...r, proposal: text } }];
Stretch
In tx, before flagging leftovers, rewrite simple calls on a single reference:
out = out.replace(/\{\{\s*(lower|upper|trim)\((\d+)\.([\w.]+)\)\s*\}\}/g, (m, fn, k, p) => {
const r = ref(k, p); const method = { lower: 'toLowerCase()', upper: 'toUpperCase()', trim: 'trim()' }[fn];
return r ? `{{ ${r}.${method} }}` : m;
});
formatDate(1.date; "YYYY-MM-DD") maps to {{ DateTime.fromISO($('Step').item.json.date).toFormat('yyyy-LL-dd') }}, but Make's tokens differ from Luxon's (YYYY → yyyy, DD → dd, MM → LL), so translate the format string too and keep it partial.