Skip to content

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 has module (app:action), mapper/parameters, an optional filter, and routers hold routes[].flow. Zapier: Settings → Data management → Export gives a zapfile; each zap has nodes keyed by id with selected_api, action, params and parent_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_map in Settings maps an app to an n8n trigger node and action node (+ the credential name); app_aliases maps 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 like formatNumber(…) 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

  1. Code Read Export (detect.js): accept objects or JSON strings; Make flow (first module is the trigger, routers recurse into routes), Zapier nodes (follow parent_id, fold Filter steps into the next step, Branching/Paths into routes).
  2. 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

  1. Code Build Report (report.js): coverage, credentials, effort, cost, checklist, Markdown.
  2. IF Valid Export? → 400 with the reason. IF Create in n8n? → Code Workflows to Create (strip id, active, pinData, which the API rejects) → n8n Workflow → Create (On Error → Continue) → Code Record Created → respond. Otherwise respond with the report.

Run a migration

  1. Export the client's scenarios/zaps. Ask for monthly run counts (Make: scenario history; Zapier: task history).
  2. 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
    
  3. Send the report as the proposal: effort, savings, what changes.
  4. 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.