czlonkowski/n8n-skills/n8n-code-javascript

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Write, review, or debug JavaScript in an n8n Code node. Use when a Code node is already in the workflow (editing it, fixing an error, choosing its mode, making it faster), or when a new one is justified: no native node fits (check the job-to-node table in using-n8n-mcp-skills first) and the job is on the closed list — loops or recursion no node expresses, state across runs, parsing no node handles, an allowlisted library, heavy all-items work, or one short node replacing 4+ glue nodes. Covers $input/$json/$node in code, this.helpers, Luxon, SplitInBatches loops, cross-iteration data, pairedItem, per-item performance, and the \"Why Code:\" note. Filtering, totals, dedupe, sorting, joins, file formats and API paging have native nodes (Filter, Summarize, Remove Duplicates, Sort, Merge, Extract From File, HTTP Request): see n8n-node-configuration. EXCEPTION — for the AI-agent-callable Custom Code Tool (@n8n/n8n-nodes-langchain.toolCode), use n8n-code-tool; it has a different runtime contract.

Install

npx vskill@latest install czlonkowski/n8n-skills/n8n-code-javascript

SKILL.md

JavaScript Code Node

Expert guidance for writing JavaScript code in n8n Code nodes.


Before you write a Code node

A new Code node has to pass this gate first:

  1. Is there a native node for the job? Check the job-to-node table in using-n8n-mcp-skills: Filter, IF / Switch,

Summarize, Aggregate, Split Out, Merge, Compare Datasets, Remove Duplicates, Sort + Limit, Date & Time, XML / HTML / Extract From File / Convert To File, HTTP Request (pagination, batching) and Crypto cover most reaches for Code. Their configs are in n8n-node-configuration → NATIVE_NODES.md.

  1. Would a short expression in that node do? One expression, no function body — the helper methods (.pluck(),

.sum(), .unique(), $ifEmpty(), .diffTo()) are in n8n-expression-syntax and, on n8n-mcp 2.97.0+, in tools_documentation({topic: "expression_helpers"}). Several statements inside {{ }} count as code, so they belong here rather than in an Edit Fields field.

  1. Is the job on the closed list? Loops or recursion no node expresses, state across runs

($getWorkflowStaticData), parsing no node handles, an allowlisted library, heavy all-items work (thousands of items, stated or measured), or one short Code node (~15 lines) replacing 4+ glue nodes that only reshape data.

If it passes, write the Code node and give it a note, so the next reader (and the review hooks) can see why:

json
{ "name": "Flatten category tree", "type": "n8n-nodes-base.code", "notes": "Why Code: Split Out considered — the tree has arbitrary depth (recursion)", "notesInFlow": true }

Editing or debugging a Code node that is already there doesn't need the gate, but if the node does a native-node job, say so and offer the native replacement.


Quick Start

javascript
// Basic template for Code nodes
const items = $input.all();
// Process data
const processed = items.map(item => ({
json: {
...item.json,
processed: true,
timestamp: new Date().toISOString()
}
}));
return processed;
Essential Rules
  1. Choose "Run Once for All Items" mode (recommended for most use cases)
  2. Access data: $input.all(), $input.first(), or $input.item
  3. Return `[{json: {...}}]` — the canonical, mode-portable form. In Run Once for All Items mode n8n also auto-wraps a bare return {…} object, so that runs too; what genuinely fails is returning a primitive (string/number) or null.
  4. CRITICAL: Webhook data is under $json.body (not $json directly)
  5. Built-ins available: this.helpers.httpRequest() (no auth — the bare $helpers global is undefined in the task-runner sandbox, so $helpers.httpRequest() throws ReferenceError: $helpers is not defined), DateTime (Luxon), $jmespath(). Not available: this.helpers.httpRequestWithAuthentication (deny-listed), $env (when N8N_BLOCK_ENV_ACCESS_IN_NODE=true), require() (unless allowlisted). For anything beyond a trivial unauthenticated GET (auth, pagination, retries), prefer the HTTP Request node and keep Code nodes for pure logic.
  6. Instance-allowlisted libraries: Self-hosted instances can allowlist modules via N8N_RUNNERS_ALLOWED_BUILT_IN_MODULES and N8N_RUNNERS_ALLOWED_EXTERNAL_MODULES (legacy: NODE_FUNCTION_ALLOW_BUILTIN / NODE_FUNCTION_ALLOW_EXTERNAL). If the user says their instance allows specific modules (e.g. axios, lodash, crypto), use them via require() — don't refuse. If unsure, ask or default to built-ins only.
  7. Wrong skill? If you're writing code for a Custom Code Tool attached to an AI Agent (@n8n/n8n-nodes-langchain.toolCode), stop — that node has a different contract (input via query, must return a string, no $input/$helpers). Use the n8n-code-tool skill.

Mode Selection Guide

The Code node offers two execution modes. Choose based on your use case:

Run Once for All Items (Recommended - Default)

Use this mode for: 95% of use cases

  • How it works: Code executes once regardless of input count
  • Data access: $input.all() or items array
  • Best for: logic that needs every item at once and passed the gate above
  • Performance: Faster for multiple items (single execution)
javascript
// Example: running balance — each item depends on the ones before it,
// which no native node expresses (Summarize gives one total per group, not a running one)
let balance = 0;
return $input.all().map(item => {
balance += item.json.amount || 0;
return { json: { ...item.json, balance } };
});

When to use:

  • ✅ Each item depends on earlier items (running totals, sessionizing events, gap detection)
  • ✅ Multi-pass algorithms or recursion over nested data of unknown depth
  • ✅ Heavy transforms over thousands of items where one node beats a long chain (measured)

Native first: totals, averages and counts → Summarize; sorting or ranking → Sort + Limit; deduplication → Remove Duplicates; items into one array → Aggregate; joining two lists → Merge.

Run Once for Each Item

Use this mode for: Specialized cases only

  • How it works: Code executes separately for each input item
  • Data access: $input.item or $item
  • Best for: Item-specific logic, independent operations, per-item validation
  • Performance: Slower for large datasets (multiple executions)
javascript
// Example: Add processing timestamp to each item
const item = $input.item;
return [{
json: {
...item.json,
processed: true,
processedAt: new Date().toISOString()
}
}];

When to use:

  • ✅ Each item needs independent API call
  • ✅ Per-item validation with different error handling
  • ✅ Item-specific transformations based on item properties
  • ✅ When items must be processed separately for business logic

Decision Shortcut:

  • Need to look at multiple items? → Use "All Items" mode
  • Each item completely independent? → Use "Each Item" mode
  • Not sure? → Use "All Items" mode (you can always loop inside)
Why "All Items" is faster — the per-item boundary

Mode choice is the single biggest performance lever in a Code node. Each per-item execution context costs a setup tax (measured on n8n 2.x, small records):

What runs per itemApprox. cost
Code All Items (one run for the whole set)~0.02 ms/item
Expression in any node (IF / Set / etc.)~0.2 ms/item
Code Each Item (a full sandbox per item)~0.6 ms/item — ~25–30× All Items

So Run Once for Each Item over 10k items is ~6 s of pure overhead vs ~0.2 s in Run Once for All Items. Use Each Item only when an item genuinely needs isolating (independent error handling, or a per-item API call you can't batch); otherwise loop inside one All Items node. Expression complexity itself is essentially free (~90% of the cost is the per-item context, not your code) and every node→node hop re-copies all items — so reduce the number of per-item boundaries, don't micro-optimize each one.

On top of the per-item cost, every Code node run pays a fixed hand-off to the task runner. In the default internal mode the runner stops after 15 s idle (N8N_RUNNERS_AUTO_SHUTDOWN_TIMEOUT), so a workflow that runs now and then pays a cold start of hundreds of milliseconds on most runs. Native transform nodes process all items in one in-process call and skip that hand-off.

What this means for node choice. Most workflows are I/O-bound, and below a few hundred items none of these costs matter: choose by readability, which means native nodes first. Performance becomes the deciding argument only for heavy all-items work — thousands of items per run, transform work rather than I/O dominating, stated by the user or measured. Then one All Items Code node can beat a long chain of nodes, and the reason goes in its Why Code: note. These figures are the pack's one measured reference; the other skills point here instead of repeating them.

See: DATA_ACCESS.md → "Mode Performance" for the corollaries, hop costs, and scale check.


Data Access Patterns

Four ways to pull data from upstream nodes. Note $node["Name"] and $('Name') need .first().json or .all() — never .json directly.

javascript
const allItems = $input.all(); // 1. All items — batch ops, aggregation (most common)
const data = $input.first().json; // 2. First item — single objects, API responses
const item = $input.item; // 3. Current item — "Each Item" mode ONLY (undefined otherwise)
const other = $node["Webhook"].json; // 4. Named node — combine data across nodes

Always access fields via .json (e.g. item.json.name, not item.name), and prefer the explicit $input.first().json.field over a bare $json.field.

See: DATA_ACCESS.md for the full guide — every pattern with examples, a decision tree, and the common mistakes (mutating originals, missing length checks, $input.item in the wrong mode).


Critical: Webhook Data Structure

MOST COMMON MISTAKE: Webhook data is nested under .body

javascript
// ❌ WRONG - Will return undefined
const name = $json.name;
const email = $json.email;
// ✅ CORRECT - Webhook data is under .body
const name = $json.body.name;
const email = $json.body.email;
// Or with $input
const webhookData = $input.first().json.body;
const name = webhookData.name;

Why: Webhook node wraps all request data under body property. This includes POST data, query parameters, and JSON payloads.

See: DATA_ACCESS.md for full webhook structure details


Return Format Requirements

Canonical form: [{json: {...}}] — an array of objects each with a json property. It is unambiguous and works identically in both execution modes, so make it your default.

In Run Once for All Items mode n8n auto-normalizes looser shapes on the way out: a single bare object, or an array of bare objects, gets wrapped under json for you. So return {foo: 1} runs. What has nothing to wrap — and therefore genuinely fails at runtime with "Code doesn't return items properly" — is a primitive (string/number/boolean) or null/undefined. (n8n-mcp ≥ 2.63.0 no longer flags a bare-object return as an error; it reflects this auto-wrap behavior.)

Correct Return Formats
javascript
// ✅ Single result
return [{
json: {
field1: value1,
field2: value2
}
}];
// ✅ Multiple results
return [
{json: {id: 1, data: 'first'}},
{json: {id: 2, data: 'second'}}
];
// ✅ Transformed array
const transformed = $input.all()
.filter(item => item.json.valid)
.map(item => ({
json: {
id: item.json.id,
processed: true
}
}));
return transformed;
// ✅ Empty result (when no data to return)
return [];
// ✅ Conditional return
if (shouldProcess) {
return [{json: processedData}];
} else {
return [];
}
Non-Canonical Returns (auto-wrapped — prefer the canonical form)
javascript
// ⚠️ Auto-wrapped in All Items mode → [{json: {field: value}}]. Runs, but prefer the array form.
return {
json: {field: value}
};
// ⚠️ Auto-wrapped → [{json: {field: value}}]. Runs, but add the json wrapper for clarity.
return [{field: value}];
// ✅ Fine — input items already carry a json property, so returning them unchanged is a valid passthrough
return $input.all();
Genuinely Broken Returns
javascript
// ❌ FAILS: primitive — n8n errors "Code doesn't return items properly"
return "processed";
// ❌ FAILS: null / undefined — nothing to pass to the next node
return null;

Why it matters: The canonical [{json: {...}}] is unambiguous and behaves the same in both modes. n8n auto-normalizes bare objects and arrays-of-objects in All Items mode, but a primitive or null return has nothing to wrap and stops execution.

See: ERROR_PATTERNS.md #3 for detailed error solutions


Common Patterns Overview

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationPassed, 82 of 100Effective. 1 run. Evaluated 7 months ago.

Source

Popularity

Repository stars
3,875
Repository forks
680
7-day trend score
41

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 28 Mar 2026, 7 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/czlonkowski/n8n-skills/n8n-code-javascript/badge)](https://verified-skill.com/skills/czlonkowski/n8n-skills/n8n-code-javascript)

Something wrong with this skill or its page? Tell us.