Zapier/Make to n8n Migration Kit
Module 23 · ship · advanced
detect.js
// Read a Make blueprint or a Zapier export and turn every scenario/zap into the same step tree:
// { key, label, app, action, kind: trigger|action|router|iterator|aggregator, params, filter, routes }
const body = $('Convert API').first().json.body ?? {};
const parse = (v) => (typeof v === 'string' ? JSON.parse(v) : v);
const appKey = (s) => String(s ?? '').replace(/@.*$/, '').replace(/API$/i, '').replace(/V\d+$/i, '').toLowerCase().replace(/[^a-z0-9]/g, '');
// --- Make: blueprint.flow[] with module "app:action", mapper/parameters, filter on the module, router routes
function makeSteps(flow, isRoot) {
return (flow ?? []).map((m, i) => {
const [app, action] = String(m.module ?? '').split(':');
const kind = m.module === 'builtin:BasicRouter' ? 'router' : m.module === 'builtin:BasicFeeder' ? 'iterator'
: /Aggregator$/.test(action ?? '') ? 'aggregator' : isRoot && i === 0 ? 'trigger' : 'action';
return {
key: String(m.id), label: m.metadata?.designer?.name || m.label || `${app} ${action}`,
app: appKey(app), app_raw: app, action: action ?? '', kind,
params: { ...(m.parameters ?? {}), ...(m.mapper ?? {}) },
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,
routes: kind === 'router' ? (m.routes ?? []).map((r, j) => ({ label: r.flow?.[0]?.filter?.name || `Route ${j + 1}`, steps: makeSteps(r.flow, false) })) : null,
};
});
}
// --- Zapier: zapfile { zaps: [{ title, nodes: { id: { selected_api, action, type_of, params, parent_id } } }] }
const 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' };
function zapSteps(nodes) {
const all = Object.values(nodes ?? {});
const kids = (id) => all.filter((n) => String(n.parent_id ?? '') === String(id ?? '')).sort((a, b) => Number(a.id) - Number(b.id));
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 }))) });
function chain(start, isRoot) {
const out = [];
let pendingFilter = null;
for (let n = start, i = 0; n; i++) {
const app = appKey(n.selected_api);
if (app === 'filter') { pendingFilter = filterOf(n); n = kids(n.id)[0]; continue; }
const children = kids(n.id);
const isPaths = /^(branching|paths)/.test(app);
out.push({
key: String(n.id), label: n.title || `${app} ${n.action ?? ''}`.trim(), app, app_raw: n.selected_api, action: n.action ?? '',
kind: isPaths ? 'router' : n.type_of === 'read' && isRoot && i === 0 ? 'trigger' : 'action',
params: n.params ?? {}, filter: pendingFilter,
routes: isPaths ? children.map((c) => ({ label: c.title || 'Path', filter: c.params?.filter ? filterOf(c) : null, steps: chain(kids(c.id)[0], false) })) : null,
});
pendingFilter = null;
if (isPaths) break;
n = children[0];
}
return out;
}
return chain(kids(null)[0] ?? all.find((n) => !n.parent_id), true);
}
let scenarios = [], error = null;
try {
if (body.blueprint || body.blueprints) {
for (const bp of (body.blueprints ?? [body.blueprint]).map(parse))
scenarios.push({ name: bp.name || 'Make scenario', source: 'make', steps: makeSteps(bp.flow, true) });
} else if (body.zapfile || body.zap) {
const zf = parse(body.zapfile ?? { zaps: [parse(body.zap)] });
for (const z of zf.zaps ?? []) scenarios.push({ name: z.title || `Zap ${z.id}`, source: 'zapier', steps: zapSteps(z.nodes) });
} else error = 'send "blueprint" (Make export), "blueprints" (array) or "zapfile" / "zap" (Zapier export)';
} catch (e) { error = `could not read the export: ${e.message}`; }
if (!error && !scenarios.some((s) => s.steps.length)) error = 'no steps found in the export';
const runs = body.monthly_runs ?? {};
return [{ json: { ok: !error, error, scenarios: scenarios.map((s) => ({ ...s, monthly_runs: Number(typeof runs === 'number' ? runs : runs[s.name] ?? 1000) })),
create: body.create === true, dry_run: body.dry_run === true } }];