On this page
- The short answer: choose by change rate and blast radius
- What the three options actually are
- The cost that decides this is maintenance, not licence
- The Canvas Line, the test to run before you pick a tool
- Where canvas tools break
- Where custom code breaks
- The hybrid that usually wins: thin canvas, fat services
- A decision table by workload shape
- Migrating without a rewrite
- Decide it this week
The short answer: choose by change rate and blast radius
Put transport on a canvas and business logic in code. A visual tool like n8n or Make is the right call when the workflow mostly moves data between systems that already have connectors, when the logic fits on one screen, and when a failed run can be replayed by hand without anyone getting hurt. Custom code is the right call when the workflow remembers something between runs, branches more than about five ways, or touches money. When both are true, which is most of the time in a real business, you want the hybrid: a thin canvas that moves things, calling versioned endpoints that decide things.
- TransportConnector auth, pagination, webhook endpoints and per-vendor retries are what a canvas is genuinely excellent at.
- StateAnything the workflow remembers between runs belongs in a database with a unique constraint, not a variable inside one execution.
- One screenA workflow you cannot read without scrolling has stopped being a diagram and become a program that happens to be drawn.
- The diffIf nobody can see a line by line diff before a change reaches production, you do not have version control. You have a save button.
These are not three points on one line running from easy to hard. They are three answers to a single question: where does the program live? On a canvas the wiring is the program and the vendor owns your control flow. In custom code you own the control flow and the vendor owns nothing. The hybrid splits them deliberately.
The feature comparison is a trap. Every canvas tool eventually adds the connector you need and every language can call an HTTP API, so the feature sets converge. What never converges is the cost of changing the thing eighteen months from now, held by a different person. Ask how often the logic changes, what breaks if a run does the wrong thing twice, and who is on call. Those three answers pick the tool, and our workflow automation builds start there.
What the three options actually are
n8n and Make are canvas tools: each node is a connector or a small code step, and the wiring between nodes is the program. Make runs hosted. n8n is commonly run hosted or self-hosted, though read its current licence terms before planning a commercial deployment around self-hosting, because those terms have changed before. Custom code means a repository, a runtime you choose, and a queue or orchestration library doing the job the canvas would have done.
- Orchestrator
- The component that decides what runs next, in what order, and what happens when a step fails. Every automation has one. The only question is whether you chose it deliberately or inherited it from whichever tool you opened first.
- Canvas tool
- A visual workflow builder in which each node is a pre-built connector or an inline code step, and the connections between nodes are the control flow. n8n and Make are canvas tools, as is almost every product marketed as no-code automation.
- Escape hatch
- The node inside a canvas tool that runs arbitrary code. Its limits set the ceiling of the whole platform: which runtime it gets, which libraries it imports, whether it reaches a private network, whether it can hold a secret.
- Blast radius
- The set of records, systems and people affected when a single run goes wrong. Blast radius, not complexity, is the property that should decide how much engineering rigour a workflow deserves.
- Metering unit
- The thing your platform bill counts: a run, a step, an operation, or a second of execution. It decides whether a loop over five hundred rows costs one unit or five hundred, so find out what yours is before designing any loop.
A canvas is not a drawing surface. It is a bundle of infrastructure you would otherwise write yourself, and it is worth being precise about what is in the bundle.
- Connector authentication, including the OAuth refresh dance you would otherwise reimplement per vendor.
- Pagination across a few hundred APIs whose documentation you will never read.
- A run history with input and output payloads captured per node, the fastest debugging surface most teams ever get.
- A trigger surface: public webhook URLs with TLS terminated, schedules, and polling with cursors, without standing up a listener.
- A screen a non-engineer can open to confirm the thing ran, which is the item code teams most often forget to replace.
Custom code gives you none of that on day one and all of the control forever. That is the trade in one sentence.
The cost that decides this is maintenance, not licence
The licence fee is the number people compare and the number that matters least. Three costs sit underneath it: what the platform meters, what changes cost in labour, and what running your own runtime costs in operations. Only the first appears on an invoice, which is why the other two are the ones that surprise people.
Start with metering, because it changes your design and not just your bill. If your plan counts operations, a loop over five hundred rows costs five hundred units, and the fix is to batch the fan-out into one call to your own endpoint that loops internally. If the plan counts execution seconds, the expensive pattern inverts: a wait node parked inside a running execution costs real money for doing nothing.
Every default below is a stand-in for you to overwrite, not a measurement. Put your own team's numbers in and the answer often flips.
It leaves out the day the canvas cannot do the thing and an engineer spends a week building a workaround inside a tool never designed to hold it. Price that week, decide how often it happens, and add it to the canvas column. Then add the migration you will run if the platform changes its metering unit, a cost that exists on only one side of this table.
The Canvas Line, the test to run before you pick a tool
The Canvas Line
Draw one line through the workflow. Above it sits transport: triggers, authentication, moving bytes, fanning out, retrying. Below it sits meaning: business rules, data shape, state, money. A canvas above the line is a gift. A canvas below the line is a liability that stays invisible for about a year. These five checks find the line for a specific workflow in under an hour.
Ask what this workflow must remember between runs: a cursor, a last-seen id, a partial batch, a lock, an approval still pending. If the honest answer is nothing, a canvas is fine. If it is anything at all, that state needs a store with a unique constraint on it. Idempotency in automation covers why the store beats the variable.
Count every if, switch, filter and error path. Under about five, a canvas stays readable to a stranger. Past that it becomes a picture of a program rather than a program, and nobody changes it confidently. Move the branching into one function with a test file and let the canvas call it once.
Ask exactly how a change gets reviewed. If the truthful answer is that somebody opens the production editor and drags a node, the workflow has no review and its blast radius is the whole business. Export, version pinning and environment promotion buy the diff back. Confirm your plan has all three.
Open the code node on your actual plan. Which runtime, which libraries, does it reach your private network, can it hold a secret without printing it into a run log? That answer is the ceiling of the platform for you. A generous escape hatch makes a canvas a deployment platform. A crippled one makes it a demo.
Write down who gets paged when this fails at two on a Sunday morning and the runbook they open. If that person cannot read the tool, you picked the wrong tool whatever it can do. This check reverses more tooling decisions than the other four combined, because it prices the two years after launch.
The line moves as a workflow matures. A nightly sync of two hundred records can become the thing finance closes the month on. Review the line once a quarter for your top five workflows and you catch that drift while it is still cheap.
Where canvas tools break
Canvas tools break at the point where a workflow stops being a pipe and starts being a program. Three failure classes cover most of it, and all three arrive quietly.
Loops, fan-out and the bill
Each item in a loop is usually a metered unit, so a batch job that reads fine on the canvas can cost far more than the same job as one call to your own endpoint. Most canvas loops also run items one at a time, so five hundred items means five hundred sequential round trips, which eventually meets an execution timeout you did not know existed. The fix is to stop looping on the canvas.
Data shaping
Mapping expressions inside node fields are code without the things that make code survivable. No test, no type, no diff, no search across the workspace, and no way to reuse one transformation twice without copying it. The day a source field changes shape you find every copy by hand.
Concurrency and ordering
Two executions of the same workflow will eventually run at once on the same record. Canvas tools rarely offer a lock, and the ones that do usually offer a per-workflow lock rather than a per-key one, which serialises everything. If two concurrent runs on one customer would corrupt something, that guarantee lives in your database as a unique constraint.
A workflow that crashes gets noticed within a day. The costly failure keeps running while quietly doing the wrong thing, usually because a node returned an empty array and the next node treated empty as success. Nobody notices until a customer does. Read error handling that stops silent failures before putting anything on a canvas that writes to a system of record.
Where custom code breaks
Custom code breaks on the days nobody is thinking about the automation. It is rarely the logic that fails. It is the surrounding work the canvas was quietly doing for free.
- Connector rot. You wrote the client for an accounting API, the vendor shipped a new major version, and the engineer who wrote it has moved on. A canvas vendor absorbs that across all their customers. You absorb it alone.
- Trigger plumbing. A public webhook endpoint needs TLS, signature verification, replay protection, a queue in front and a dead letter behind. That is a week that never makes the estimate because it sounds like one route handler.
- Observability. A canvas records the payload at every node by default. Teams who skip building that in code end up debugging production by adding log lines and waiting for the failure to repeat.
- Visibility for everyone else. Nobody outside engineering can confirm the thing ran, so the team becomes a lookup service for the question did it work.
- The three line change. Someone wants one more field in a Slack message. On a canvas that is a minute. In code it is a branch, a review, a deploy and a busy person, so the request waits.
This is why the pure code answer is wrong more often than engineers expect. The code is not the expensive part. The scaffolding is, and the canvas vendors already built it and are amortising it across thousands of customers.
The hybrid that usually wins: thin canvas, fat services
Systems that survive three years tend to look alike. A thin canvas moves things and does nothing else, calling a small number of versioned HTTP endpoints that do all the deciding. The canvas keeps triggers, auth, connectors, per-node run history and a screen a non-engineer can read. The code keeps rules, state, money, anything worth a test.
The artifact that makes the split real is a one page contract written before anyone opens a tool. It forces the four decisions people otherwise discover in production. Copy this and keep it in the repository beside the service it describes.
{
"name": "invoice-to-ledger",
"owner": "ops-platform",
"on_call": "#ops-oncall",
"trigger": {
"kind": "webhook",
"source": "billing-provider",
"signature_header": "X-Signature",
"replay_window_seconds": 300
},
"idempotency": {
"key": "provider_event_id",
"store": "postgres:automation.processed_events",
"unique_constraint": true,
"ttl_days": 30
},
"above_the_line_on_canvas": [
"receive webhook", "verify signature", "enqueue",
"call decision service", "write to system of record", "notify humans"
],
"below_the_line_in_code": {
"service": "ledger-rules",
"endpoint": "POST /v1/classify",
"pinned_version": "2026-08-01",
"tests": "repo:ledger-rules/tests"
},
"retry": {
"attempts": 5, "strategy": "exponential",
"base_ms": 500, "cap_ms": 60000, "jitter": "full",
"retry_on_status": [408, 425, 429, 500, 502, 503, 504],
"never_retry_on_status": [400, 401, 403, 404, 409, 422]
},
"dead_letter": {
"sink": "postgres:automation.dlq",
"human_review": "Monday 09:00",
"alert_after_items": 1
},
"budget": {
"metering_unit": "READ YOUR PLAN AND WRITE IT HERE",
"max_units_per_run": 12,
"alert_at_percent_of_plan": 80
},
"slo": {
"p95_end_to_end_seconds": 120,
"heartbeat": "every run writes a row, alert if no rows in 6h"
},
"exit_plan": "all decisions are HTTP calls, so replacing the canvas rewires triggers only"
}Two fields do most of the work. The idempotency key decides whether a retry is safe, which decides whether you can automate retries at all. The metering unit decides your loop design. For the retry numbers, rate limits, retries and backoff explains why jitter matters more than attempt count.
A decision table by workload shape
| Workload shape | Start here | Why | The trap |
|---|---|---|---|
| Move records between two SaaS systems on a schedule | Canvas | Connectors, auth and pagination are the entire job | Field mapping grows into business rules nobody can test |
| React to a webhook and post a message | Canvas | Trigger plumbing is the expensive part and the tool owns it | No signature check, so anyone who finds the URL drives your workflow |
| Anything writing to a ledger, an invoice or a payment | Code, called by a canvas | Blast radius is money and every replay must be provably safe | Retries with no idempotency key, which is how one refund becomes three |
| Enrich tens of thousands of rows nightly | Code | Per-operation metering and per-run timeouts fight you at once | Looping on the canvas and meeting the bill after the month closes |
| Multi-step approval with waits measured in days | Code with a durable store | A run parked mid-flight is state, and state belongs in a database | Wait nodes holding executions open until a platform restart loses them |
| LLM calls sitting in the path | Either, with an eval harness | The model is a dependency that changes with no release note | No pinned prompt and no output schema, so format drift breaks the next node |
Read the trap column first. The trap decides the tool more reliably than the recommendation does, because every recommendation here is defensible and every trap is expensive. If you cannot defuse the trap inside the tool you prefer, that is your answer.
Migrating without a rewrite
You do not choose once and live with it. Moving logic off a canvas is straightforward in the right order, and the right order is counterintuitive: the trigger moves last, because the trigger is the part the canvas does best.
- Freeze the canvas
Stop adding logic to the tool you plan to leave. New rules go into a service from today. This costs nothing and stops the problem growing while you work.
- Extract the rules into one function
Copy every mapping expression, filter and branch into a single pure function that takes a payload and returns a decision, with no network calls inside. Write the tests from real payloads pulled out of the canvas run history, which is exactly what that history is for.
- Put the function behind an endpoint
Deploy it as one HTTP route returning the decision as JSON. Version the route in the path or a header and pin the canvas to that version, so a later deploy cannot change behaviour under a live workflow.
- Shadow run both
Have the canvas call the endpoint and log any disagreement with its own logic without acting on it. Every disagreement is either a bug in the new code or an undocumented rule in the old canvas, and you need to know which before you cut.
- Cut over, move the trigger last
Delete the canvas logic and keep the canvas as transport. Move the trigger into your own infrastructure only if the vendor becomes a problem, because by then that rewrite is a webhook handler and a queue rather than a business system.
The sequence works in reverse too. Teams drowning in trigger plumbing can move triggers onto a canvas first and leave the services untouched, which is also the ordinary path off brittle bots in migrating off RPA.
Decide it this week
This decision does not need a two week evaluation. It needs one workflow, one hour and honest answers.
If seven of the eight have clean answers, build it. If only the exit plan is blank, build it and fix that in month two. If the state question or the on-call question is blank, stop, because those two turn into an incident.
ChatGPTalker, Choosing Between n8n, Make and Custom Code: put transport on a canvas and business logic behind a versioned endpoint, and settle the split with the Canvas Line test rather than a feature comparison.
Questions readers ask next
Is n8n better than Make?
Can I run an entire business on a canvas tool?
When should I move logic off a canvas tool?
Does self-hosting n8n save money?
How do I version control a visual workflow?
Should I use the built-in LLM nodes in these tools?
ChatGPTalker. "n8n vs Make vs Custom Code: How to Actually Choose." chatgptalker.com, 2026-08-26. https://chatgptalker.com/guides/n8n-make-or-custom-code/