Zapier/Make to n8n Migration Kit
Module 23 · ship · advanced
Lesson 23 · Zapier/Make to n8n Migration Kit
Goal
Businesses outgrow Zapier and Make when every task costs money. Moving to n8n is a common freelance job, and the slow part is the first pass: listing every step, finding the n8n node, rewriting every field reference. Build a converter that does that first pass and writes an honest report.
Prerequisites
- Modules 01–04 (n8n basics, expressions, error handling), 12 (self-hosting: the cost comparison assumes it)
- Optional: an n8n API key (Settings → n8n API) to create the converted workflows automatically
Concepts
- Exports. Make: Scenario → … → Export Blueprint gives JSON with
flow[]; each module hasmodule(app:action),mapper/parameters, an optionalfilter, and routers holdroutes[].flow. Zapier: Settings → Data management → Export gives a zapfile; each zap hasnodeskeyed by id withselected_api,action,paramsandparent_id(the export format isn't publicly documented, so test with a real file first). - Normalize first. Both formats become one step tree (
trigger / action / router / iterator / aggregator, with filters and routes). Everything after that is format-independent, which is also how you'd add a third source later. - The mapping table is data.
app_mapin Settings maps an app to an n8n trigger node and action node (+ the credential name);app_aliasesmaps the many names apps go by (google-email,GoogleMailV2API→gmail). Adding an app means adding a line, not code. - Expression rewriting.
{{1.email}}(Make) and{{123__email}}(Zapier) point at an earlier step's output. They become{{ $('Step name').item.json.email }}. n8n's Webhook node puts the request body under.body, so references to a webhook trigger get.body.in front. Functions likeformatNumber(…)can't be translated safely, so they're flagged. - Structure. A filter becomes an If node in front of the step. A Make router or Zapier paths becomes a Switch with one output per route and Send data to all matching outputs on, because both tools run every route whose condition passes.
- Honest statuses. mapped: node and fields filled in. partial: right node, check the settings (original settings are in the node's notes). manual: no equivalent in the table: a placeholder No Op node marks the spot.
- Estimates, clearly labeled. Zapier bills tasks (actions, not the trigger or filters); Make bills operations (every module run, not routers or filters). With monthly runs per scenario and the rates in Settings you get a ballpark old cost against a flat self-hosted n8n server. Effort = hours per mapped/partial/manual step + testing.
Build it
Step 1: Triggers, Settings, Route
Webhooks Convert API (POST mig/convert) and Mappings API (GET mig/mappings) → Edit Fields Settings (edit me): app_map, app_aliases, hours per status, hourly rate, Zapier/Make rates, n8n monthly cost → Switch Route.
Gotcha: app_map is an object written as an n8n expression. A nested object ends in }}, which closes the expression early. Write } } with a space.
Step 2: Read and convert
- Code
Read Export(detect.js): accept objects or JSON strings; Makeflow(first module is the trigger, routers recurse into routes), Zapiernodes(followparent_id, fold Filter steps into the next step, Branching/Paths into routes). - Code
Convert to n8n(convert.js): register unique node names first (so references resolve), then walk the tree: If for filters, Switch for routers, Split Out / Aggregate for iterators/aggregators, the mapped node otherwise, filling the fields it knows (Gmail, Slack, HTTP Request, Google Sheets, Set, Wait, Code stub). No trigger → a Schedule Trigger is added. Workflow ids are a stable hash, so re-converting overwrites instead of duplicating.
Step 3: Report and respond
- Code
Build Report(report.js): coverage, credentials, effort, cost, checklist, Markdown. - IF
Valid Export?→ 400 with the reason. IFCreate in n8n?→ CodeWorkflows to Create(stripid,active,pinData, which the API rejects) → n8n Workflow → Create (On Error → Continue) → CodeRecord Created→ respond. Otherwise respond with the report.
Run a migration
- Export the client's scenarios/zaps. Ask for monthly run counts (Make: scenario history; Zapier: task history).
- Convert:
curl -X POST http://localhost:5678/webhook/mig/convert -H 'Content-Type: application/json' \ -d "$(jq -n --slurpfile b samples/make-blueprint.json '{blueprint: $b[0], monthly_runs: 2000}')" | jq -r .report_markdown - Send the report as the proposal: effort, savings, what changes.
- Import (or
create: true), add credentials, fix partial/manual steps, run both in parallel for a week, then switch over (the checklist in the report).
Test it
./test/test.sh # 41 assertions, includes importing the generated workflows into n8n
WITH_CREDS=1 ./test/test.sh # also creates through the n8n API
Common problems
- "could not read the export": the file isn't JSON, or it's a Make scenario list rather than a blueprint. Export the blueprint per scenario.
- A step shows "isn't in the mapping table" although n8n has that node: add it to
app_map(and an alias if the export uses another name). - Expressions point at the wrong field: Make and Zapier output shapes differ from n8n's (e.g. Stripe triggers, Sheets rows). Run the new trigger once and adjust paths.
- Create fails with "must NOT have additional properties": the API only accepts name, nodes, connections and settings.