n8n Function Node Tutorial: Custom Code in Workflows

The Function node is where n8n stops being a no-code tool and starts being a real automation platform. When the built-in nodes don't cover your logic — transforming data in a non-standard way, running

The Function node is where n8n stops being a no-code tool and starts being a real automation platform. When the built-in nodes don't cover your logic — transforming data in a non-standard way, running conditional branching that's too complex for IF nodes, or massaging an API response before passing it downstream — you drop into JavaScript and handle it yourself. Here's how to use it effectively.

What the Function Node Actually Does

The Function node gives you a JavaScript execution context with access to the input items from the previous node. You return an array of items, and n8n passes those downstream. That's the entire contract. The key object you work with is items — an array where each element has a json property containing your data.

  • Read input: items[0].json.fieldName
  • Write output: return an array like [{ json: { result: 'value' } }]
  • Loop over items: use items.map() or a for loop when you have multiple records
  • Access built-ins: $now, $today, $workflow, $execution are available without importing

The most common mistake is forgetting to wrap your output in the expected structure. n8n expects [{ json: {...} }] — if you return a plain object or a raw array of strings, the downstream node gets nothing or throws.

Practical Patterns You'll Use Constantly

A few patterns cover the majority of real-world Function node usage. Know these and you'll handle most cases without reaching for docs.

  • Flatten a nested object: APIs often return deeply nested JSON. Use destructuring or manual dot-notation to pull what you need into a flat output for easier downstream mapping.
  • Filter items conditionally: Return only items matching a condition. return items.filter(item => item.json.status === 'active'); — clean and readable.
  • Aggregate multiple items into one: Collect all items into a single output record using reduce(). Useful before an HTTP node that expects a batch payload.
  • Format dates: n8n's date handling across nodes can be inconsistent. Inside Function, use new Date(item.json.created_at).toISOString() to normalize before storing or sending.
  • Generate dynamic values: Build slugs, hash IDs, construct URLs from parts — anything requiring string manipulation that expression fields can't handle cleanly.

One non-obvious thing: you can console.log() inside the Function node and see output in n8n's execution log. Use this aggressively when debugging. Print items at the top to confirm what you're actually working with before writing the transformation.

Error Handling Inside the Function Node

By default, any thrown error in a Function node halts the execution. That's usually what you want — but sometimes you need to handle partial failures gracefully, especially when iterating over multiple items.

  • Wrap risky operations in try/catch and return a fallback value instead of throwing
  • Use the continueOnFail option in node settings if you want n8n to route errors to an Error branch instead of stopping cold
  • For batch operations, process each item in a loop with individual try/catch blocks so one bad record doesn't kill the rest
  • Add an _error field to failed items and filter them downstream — gives you visibility without stopping the workflow

If your Function node is doing anything non-trivial with external state or complex conditionals, test it in isolation first. Create a manual trigger, hardcode a sample input, and validate the output before wiring it into the full workflow.

When Not to Use the Function Node

The Function node is powerful, but reaching for it too quickly is a sign the workflow needs rethinking. A few cases where you should step back:

  • If you're writing more than 30–40 lines, consider whether this should be an external service call instead of inline code
  • If the logic is reused across multiple workflows, extract it — either into a sub-workflow called via the Execute Workflow node, or into an HTTP endpoint
  • If you need external npm packages, you'll hit the sandbox limitation. n8n doesn't allow arbitrary package imports in the hosted version without self-hosting and configuring NODE_FUNCTION_ALLOW_EXTERNAL
  • Simple field renaming or value extraction is better handled with the Set node — it's faster to configure, easier for non-developers to read, and creates less maintenance surface

The Function node is a precision tool. Use it for the cases where expression syntax falls short and no built-in node fits — not as the default answer to every data problem.

If you're building workflows from scratch and want to skip the setup time on common patterns, check out these ready-made n8n templates — production-grade workflows you can import and adapt instead of starting from a blank canvas. The Function node logic in each one is documented and follows the patterns above, so they're also useful as reference implementations when you're learning how to structure complex transformations.