My decision rule after running both in production: n8n for integration-heavy workflows that non-technical people need to see and edit, custom code for anything that needs testing, evaluation hooks, or sub-second latency — and a hybrid of the two for most serious agent systems. The 'no-code vs code' framing misses the point; both tools work. The real question is where each one's failure modes land on your specific project, and that is decidable with a short checklist rather than a philosophy debate.
The real question is where the failure modes hurt
Every automation tool fails somewhere; you are choosing which failures you can live with. n8n's failures are about scale of logic: workflows that grow past a certain complexity become harder to reason about than the equivalent code, and testing them properly is awkward. Custom code's failures are about scale of maintenance: every integration, retry policy, and scheduler you hand-roll is code you now own forever, and it is invisible to anyone who cannot read it. Neither failure mode is theoretical — I have hit both — so the framework below is really a map of which one your project is more exposed to.
Where n8n wins
n8n earns its place in three situations. First, integration breadth: when a workflow touches many third-party APIs — a CRM, a spreadsheet, an email provider, a chat webhook — n8n's prebuilt nodes wire it up in hours, versus days of reading API docs and writing auth handling. Second, stakeholder visibility: the visual canvas is genuinely auditable by non-developers, which matters when a client or an ops lead needs to understand and occasionally tweak what the automation does without opening an editor. Third, infrastructure you would otherwise build: retries, error branches, cron scheduling, webhook endpoints, and execution logs come for free, and a solo builder should not underestimate how much time that saves.
Where n8n starts to hurt
The pain arrives on predictable fronts. Complex conditional logic turns the canvas into spaghetti — a decision tree that reads as fifteen clean lines of TypeScript becomes a tangle of IF nodes nobody wants to touch. The testing story is weak: there is no natural way to unit-test a node graph the way you test a function, so verification stays manual. Version control chafes: workflows export as large JSON blobs, and a git diff of two exports tells you almost nothing about what actually changed — a real problem when you want to review changes before they run against production systems. And cost structure matters at volume: cloud pricing is per-execution, so high-frequency workflows get expensive, while self-hosting is cheap in cash but makes you the person responsible for uptime, upgrades, and backups.
Where custom code wins
Code wins wherever the logic itself is the product. If the workflow needs evaluation hooks — scoring outputs, gating deploys on eval results — that harness wants to live in a codebase, not a node graph. Same for observability: emitting structured traces from every step is natural in code and clumsy in n8n. Sub-second latency requirements rule out a workflow engine's overhead entirely. Complex state machines with many branches read far better as code, sit in the same test suite as everything else, and go through the same pull-request review. And anything you expect to maintain for years benefits from the boring virtues: types, tests, diffs, and refactoring tools.
The hybrid pattern that actually works
The setup I keep landing on treats n8n as the shell and code as the core. n8n owns the edges: webhook triggers, schedules, third-party API calls, retry policies, and the visual overview a stakeholder can audit. The reasoning-heavy or latency-sensitive middle is a custom service the workflow calls through an HTTP node — or, increasingly, through n8n's MCP support, which lets a flow expose or consume Model Context Protocol tools and slots an agent's tool-calling loop cleanly into a visual workflow. You get n8n's integration speed without trapping your core logic somewhere it cannot be tested, and you can migrate either layer independently later.
A decision checklist — five questions
Before choosing, answer these five. Who maintains this in a year — a developer, or whoever is at the desk? Non-developer maintenance points to n8n. How many third-party APIs does it touch? Three or more favours n8n's node library. What is the latency budget? Anything user-facing and sub-second says code. Does the logic need to be evaluated or unit-tested? If quality gates matter, the core belongs in code. Who needs to see the flow? If a client or manager must audit it visually, that is n8n's strongest single argument. Two or more answers pointing the same direction is usually a clear call; a split verdict is the hybrid pattern's cue.
What this looked like in practice
My Google Ads automation is the worked example. The orchestration layer — schedules, fetching account data through APIs, routing alerts — runs through n8n with MCP in the stack, because that layer is integration plumbing and benefits from being visible and editable without a deploy. The optimisation logic that decides what to change sits in code, where it can be tested and traced, because a system adjusting live ad spend is exactly the place I want evaluation gates rather than vibes. That split took weekly optimisation work from nine hours to two and helped drive an 18% ROAS improvement quarter over quarter — and the division of labour, not either tool alone, is what made it maintainable. I wrote up the build in the Google Ads automation with n8n tutorial if you want the full wiring.
Related reading
If this is useful, a related build breakdown — the $999/mo growth service.