N8n Code Javascript
Romuald CzlonkowskiCommunityCreated by Romuald Czlonkowski
- Skill
- Tool integration
- Automation and integrations
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured approach to using the n8n Code node. It emphasizes a 'native-first' philosophy, requiring users to verify if a built-in node or expression can handle the task before resorting to custom JavaScript. It covers essential execution modes, data access patterns, return formats, and sandbox limitations, ensuring that custom code is performant, maintainable, and correctly integrated into n8n workflows.
What it does
- Guides users through the decision gate to determine if a Code node is necessary.
- Explains the difference between 'Run Once for All Items' and 'Run Once for Each Item' modes.
- Provides canonical patterns for data access and returning processed items.
- Details sandbox restrictions, including built-in functions and library allowlisting.
- Offers best practices for error prevention, null checking, and performance optimization.
- Standardizes the use of 'Why Code:' notes for better workflow documentation.
When to use it
- When no native n8n node exists for a specific data transformation or logic task.
- For complex operations like recursion, state management across runs, or parsing custom formats.
- When a single short Code node can replace a long, inefficient chain of multiple glue nodes.
- When handling heavy all-items processing that requires optimized performance.
- When working with allowlisted external libraries not covered by native nodes.
Advantages
- Reduces workflow complexity by replacing long chains of nodes with concise logic.
- Improves performance by choosing the correct execution mode for the dataset size.
- Ensures code maintainability through standardized documentation and return formats.
- Prevents common runtime errors related to webhook structures and sandbox limitations.
- Promotes the use of native nodes for standard tasks, keeping workflows cleaner.
Usage examples
- “How do I calculate a running total for my items in n8n?”
- The agent will recommend using a Code node in 'Run Once for All Items' mode, providing a JavaScript snippet that iterates through the items and maintains a running balance variable.
- “I have a Code node that is failing with a 'Code doesn't return items properly' error. How do I fix it?”
- The agent will guide you to wrap your output in the canonical [{json: {...}}] format and ensure your code does not return a primitive value or null.
- “Should I use a Code node to filter my data?”
- The agent will advise against it, suggesting the native Filter node instead, and explain how to check the job-to-node table to confirm if a native alternative exists.
- “How do I access webhook data inside a Code node?”
- The agent will clarify that webhook data is nested under $json.body and provide the correct syntax to access fields like $input.first().json.body.fieldName.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/czlonkowski/n8n-code-javascript/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip n8n-code-javascript.zip -d .agents/skills
Documentation by Romuald Czlonkowski
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:
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 inn8n-node-configuration→ NATIVE_NODES.md.Would a short expression in that node do? One expression, no function body — the helper methods (
.pluck(),.sum(),.unique(),$ifEmpty(),.diffTo()) are inn8n-expression-syntaxand, on n8n-mcp 2.97.0+, intools_documentation({topic: "expression_helpers"}). Several statements inside{{ }}count as code, so they belong here rather than in an Edit Fields field.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:
{ "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
// 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
Choose "Run Once for All Items" mode (recommended for most use cases)
Access data:
$input.all(),$input.first(), or$input.itemReturn
[{json: {...}}]— the canonical, mode-portable form. In Run Once for All Items mode n8n also auto-wraps a barereturn {…}object, so that runs too; what genuinely fails is returning a primitive (string/number) ornull.CRITICAL: Webhook data is under
$json.body(not$jsondirectly)Built-ins available:
this.helpers.httpRequest()(no auth — the bare$helpersglobal is undefined in the task-runner sandbox, so$helpers.httpRequest()throwsReferenceError: $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.Instance-allowlisted libraries: Self-hosted instances can allowlist modules via
N8N_RUNNERS_ALLOWED_BUILT_IN_MODULESandN8N_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 viarequire()— don't refuse. If unsure, ask or default to built-ins only.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 viaquery, 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()oritemsarrayBest for: logic that needs every item at once and passed the gate above
Performance: Faster for multiple items (single execution)
// 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.itemor$itemBest for: Item-specific logic, independent operations, per-item validation
Performance: Slower for large datasets (multiple executions)
// 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 item | Approx. 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.
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 nodesAlways 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
// ❌ 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
// ✅ 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)
// ⚠️ 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
// ❌ 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
The most useful Code node shapes from production workflows, for when Code passed the gate. Several of them have a native equivalent (totals → Summarize, top-N → Sort + Limit, JSON comparison → Compare Datasets, string aggregation → Aggregate or Summarize concatenate); each pattern in the library opens with its native alternative. The full library covers 10 patterns: multi-source aggregation, regex filtering, markdown/structured-text parsing, JSON comparison, CRM/form transformation, release processing, array transformation with computed fields, Slack Block Kit formatting, top-N ranking, and string-aggregation reporting — each with variations.
See: COMMON_PATTERNS.md for the 10 detailed production patterns (and the Best Practices section: validate input, try-catch, filter-early, array methods over loops, console.log debugging).
Error Prevention - Top Mistakes
The recurring Code node failures, in rough frequency order:
Empty code / missing return — always end with
return [...], and make sure every branch returns.Expression syntax as code — don't write
{{ }}where JavaScript belongs (return {{ $json.x }}is a syntax error). Use`${$json.field}`or$input.first().json.field.{{ }}inside a string literal is fine — it's just literal text n8n won't evaluate.Return shape — prefer
return [{json:{...}}]. A barereturn {…}auto-wraps in All Items mode, but returning a primitive (string/number) ornullis what actually fails.Missing null checks — use optional chaining:
item.json?.user?.email || 'fallback'.Webhook body nesting —
$json.emailis undefined; use$json.body.email.Auth helpers blocked (
httpRequestWithAuthentication) and$envblocked — route secrets through credentials/HTTP Request node, not the Code node sandbox.
See: ERROR_PATTERNS.md for the comprehensive guide — each error with wrong/right code, escaping rules, the sandbox restrictions (Errors #6–#7), a prevention checklist, and a quick error-message lookup table.
Built-in Functions & Helpers
// HTTP requests (no auth — see sandbox note below)
const res = await this.helpers.httpRequest({ method: 'GET', url: 'https://api.example.com/data' });
// DateTime (Luxon): now, formatting, arithmetic
const now = DateTime.now();
const formatted = now.toFormat('yyyy-MM-dd');
const tomorrow = now.plus({ days: 1 });
// $jmespath() — query JSON structures
const adults = $jmespath($input.first().json, 'users[?age >= `18`]');
// $getWorkflowStaticData() — data that persists across executionsSandbox (since n8n v2.0, JsTaskRunnerSandbox): the accessor is this.helpers.httpRequest() — the bare $helpers global is undefined here ($helpers.httpRequest() throws ReferenceError). Inside a nested async function where this is lost, call it as await fn.call(this, ...). this.helpers.httpRequestWithAuthentication and this.helpers.requestWithAuthenticationPaginated are deny-listed (→ UnsupportedFunctionError); for authenticated calls use an HTTP Request node with the credential (preferred), a sub-workflow, or a manual Authorization: Bearer ${token} header on this.helpers.httpRequest() only when the token already flows through the workflow as data. $env is blocked when N8N_BLOCK_ENV_ACCESS_IN_NODE=true; require() works only for allowlisted modules. Buffer, URL, and standard JS globals (Math, JSON, Object, Array) always work.
See: BUILTIN_FUNCTIONS.md for the complete reference — full httpRequest options, all DateTime/Luxon operations, JMESPath patterns, static-data use cases, and the sandbox-restriction details.
Best Practices
Validate input first — guard for empty arrays / missing
.jsonbefore processing.Try-catch risky work (HTTP calls) and return an error object instead of crashing.
Prefer array methods (
filter/map/reduce) over manual loops.Filter early, transform late — shrink the dataset before expensive work.
Descriptive names and
console.log()for debugging (output goes to the browser console).
See: COMMON_PATTERNS.md → "Best Practices" for code examples of each.
Production Gotchas
Hard-won lessons from real deployments — summarized here, with code in DATA_ACCESS.md → "Production Gotchas":
SplitInBatches outputs are counterintuitive:
main[0]= done (fires once, after all batches),main[1]= each batch (the loop body). Add a Limit 1 node after the done output as a safety.Iteration count is the cost: each loop iteration re-runs the whole body through the engine (~0.8 ms overhead each).
batchSize: 1is the loop equivalent of Each Item — use the largest batch your real constraint (rate limit, page size, memory) allows, or don't loop at all.Cross-iteration accumulation (CRITICAL): after the loop,
$('Node Inside Loop').all()returns ONLY the last iteration's items. Accumulate via$getWorkflowStaticData('global')(reset before, push inside, read after).pairedItem: when emitting items that don't map 1:1 to input, set
pairedItem: { item: i }or downstream Set nodes fail withpaired_item_no_info.Node reference syntax:
$('Node').first().jsonor$('Node').all()— never.jsondirectly on the reference.Float precision: compare currency at the cent level —
Math.round(a*100) !== Math.round(b*100)— to avoid false positives from float noise.
When to Use Code Node
Code is the last rung, after the service's own node, a native node for the job, and a short expression inside that node. The gate at the top of this skill is the test; this is the closed list it checks against:
| Code is right for | Example | Not Code |
|---|---|---|
| Loops or recursion no node expresses | flatten a tree of unknown depth; multi-pass matching | one level of nesting → Split Out |
| State across runs | counters or cursors in $getWorkflowStaticData | "only new items" → Remove Duplicates across executions |
| Parsing no node handles | fixed-width records, a custom log format | CSV / XLSX / PDF / XML / HTML → Extract From File, XML, HTML |
| An allowlisted library | require('some-lib') the instance allows | hashing / HMAC / UUID → Crypto |
| Heavy all-items work | thousands of items, transform-bound, stated or measured | a few hundred items → native nodes |
| One short node replacing 4+ glue nodes | ~15 lines that would otherwise be a Set → Split Out → Set → Aggregate chain that only reshapes data | a chain with real routing or I/O in it |
Not on the list, so not Code: filtering (Filter), routing (IF / Switch), totals and counts (Summarize), top-N (Sort + Limit), dedupe (Remove Duplicates), joins (Merge), diffs (Compare Datasets), dates (Date & Time or a Luxon expression), API calls and paging (HTTP Request), crypto and XML (Crypto, XML).
Every Code node that stays carries the note Why Code: <native node considered> — <reason> (set notes, and
notesInFlow: true to show it on the canvas). The create/update hooks remind you when a Code node has no such note.
Integration with Other Skills
Works With:
n8n Expression Syntax:
Expressions use
{{ }}syntax in other nodesCode nodes use JavaScript directly (no
{{ }})When to use expressions vs code
n8n MCP Tools Expert:
How to find Code node:
search_nodes({query: "code"})Get configuration help:
get_node({nodeType: "nodes-base.code"})Validate code:
validate_node({nodeType: "nodes-base.code", config: {...}})
n8n Node Configuration:
Mode selection (All Items vs Each Item)
Language selection (JavaScript vs Python)
Understanding property dependencies
n8n Workflow Patterns:
Code nodes in transformation step
Webhook → Code → API pattern
Error handling in workflows
n8n Validation Expert:
Validate Code node configuration
Handle validation errors
Auto-fix common issues
Quick Reference Checklist
Before deploying Code nodes, verify:
[ ] Passed the gate - No native node fits;
notesstarts withWhy Code:[ ] Code is not empty - Must have meaningful logic
[ ] Return statement exists - Returns items, not a primitive/
null[ ] Canonical return format - Each item:
{json: {...}}(bare objects auto-wrap, but be explicit)[ ] Data access correct - Using
$input.all(),$input.first(), or$input.item[ ] No
{{ }}written as code - Use JavaScript template literals:`${value}`[ ] Error handling - Guard clauses for null/undefined inputs
[ ] Webhook data - Access via
.bodyif from webhook[ ] Mode selection - "All Items" for most cases
[ ] Performance - Prefer map/filter over manual loops
[ ] Output consistent - All code paths return same structure
Additional Resources
Related Files
DATA_ACCESS.md - Comprehensive data access patterns
COMMON_PATTERNS.md - 10 production-tested patterns
ERROR_PATTERNS.md - Top 5 errors and solutions
BUILTIN_FUNCTIONS.md - Complete built-in reference
n8n Documentation
Code Node Guide: https://docs.n8n.io/code/code-node/
Built-in Methods: https://docs.n8n.io/code-examples/methods-variables-reference/
Luxon Documentation: https://moment.github.io/luxon/
Ready to write JavaScript in n8n Code nodes! Start with simple transformations, use the error patterns guide to avoid common mistakes, and reference the pattern library for production-ready examples.
Files
- ATTRIBUTION.mdAdded by AutoGineer895 B
- BUILTIN_FUNCTIONS.md23.5 KiB
- COMMON_PATTERNS.md32.9 KiB
- DATA_ACCESS.md22.2 KiB
- ERROR_PATTERNS.md23.4 KiB
- LICENSELicence file added by AutoGineer1.1 KiB
- README.md9.8 KiB
- SKILL.md22.4 KiB
DATA_ACCESS.md
Data Access Patterns - JavaScript Code Node
Comprehensive guide to accessing data in n8n Code nodes using JavaScript.
Overview
In n8n Code nodes, you access data from previous nodes using built-in variables and methods. Understanding which method to use is critical for correct workflow execution.
Data Access Priority (by common usage):
$input.all()- Most common - Batch operations, aggregations$input.first()- Very common - Single item operations$input.item- Common - Each Item mode only$node["NodeName"].json- Specific node references$json- Direct current item (legacy, use$inputinstead)
Native first. These patterns show how to read data once a Code node passed the gate in SKILL.md. Several examples here (filter, sort and limit, dedupe, sum, grouping) are native-node jobs in a real workflow: Filter, Sort + Limit, Remove Duplicates, Summarize. Use them as Code idioms, not as a reason to add a Code node.
Pattern 1: $input.all() - Process All Items
Usage: Most common pattern for batch processing
When to use:
Processing multiple records
Aggregating data (sum, count, average)
Filtering arrays
Transforming datasets
Comparing items
Sorting or ranking
Basic Usage
// Get all items from previous node
const allItems = $input.all();
// allItems is an array of objects like:
// [
// {json: {id: 1, name: "Alice"}},
// {json: {id: 2, name: "Bob"}}
// ]
console.log(`Received ${allItems.length} items`);
return allItems;Example 1: Filter Active Items
const allItems = $input.all();
// Filter only active items
const activeItems = allItems.filter(item => item.json.status === 'active');
return activeItems;Example 2: Transform All Items
const allItems = $input.all();
// Map to new structure
const transformed = allItems.map(item => ({
json: {
id: item.json.id,
fullName: `${item.json.firstName} ${item.json.lastName}`,
email: item.json.email,
processedAt: new Date().toISOString()
}
}));
return transformed;Example 3: Aggregate Data
const allItems = $input.all();
// Calculate total
const total = allItems.reduce((sum, item) => {
return sum + (item.json.amount || 0);
}, 0);
return [{
json: {
total,
count: allItems.length,
average: total / allItems.length
}
}];Example 4: Sort and Limit
const allItems = $input.all();
// Get top 5 by score
const topFive = allItems
.sort((a, b) => (b.json.score || 0) - (a.json.score || 0))
.slice(0, 5);
return topFive.map(item => ({json: item.json}));Example 5: Group By Category
const allItems = $input.all();
// Group items by category
const grouped = {};
for (const item of allItems) {
const category = item.json.category || 'Uncategorized';
if (!grouped[category]) {
grouped[category] = [];
}
grouped[category].push(item.json);
}
// Convert to array format
return Object.entries(grouped).map(([category, items]) => ({
json: {
category,
items,
count: items.length
}
}));Example 6: Deduplicate by ID
const allItems = $input.all();
// Remove duplicates by ID
const seen = new Set();
const unique = [];
for (const item of allItems) {
const id = item.json.id;
if (!seen.has(id)) {
seen.add(id);
unique.push(item);
}
}
return unique;Pattern 2: $input.first() - Get First Item
Usage: Very common for single-item operations
When to use:
Previous node returns single object
Working with API responses
Getting initial/first data point
Configuration or metadata access
Basic Usage
// Get first item from previous node
const firstItem = $input.first();
// Access the JSON data
const data = firstItem.json;
console.log('First item:', data);
return [{json: data}];Example 1: Process Single API Response
// Get API response (typically single object)
const response = $input.first().json;
// Extract what you need
return [{
json: {
userId: response.data.user.id,
userName: response.data.user.name,
status: response.status,
fetchedAt: new Date().toISOString()
}
}];Example 2: Transform Single Object
const data = $input.first().json;
// Transform structure
return [{
json: {
id: data.id,
contact: {
email: data.email,
phone: data.phone
},
address: {
street: data.street,
city: data.city,
zip: data.zip
}
}
}];Example 3: Validate Single Item
const item = $input.first().json;
// Validation logic
const isValid = item.email && item.email.includes('@');
return [{
json: {
...item,
valid: isValid,
validatedAt: new Date().toISOString()
}
}];Example 4: Extract Nested Data
const response = $input.first().json;
// Navigate nested structure
const users = response.data?.users || [];
return users.map(user => ({
json: {
id: user.id,
name: user.profile?.name || 'Unknown',
email: user.contact?.email || 'no-email'
}
}));Example 5: Combine with Other Methods
// Get first item's data
const firstData = $input.first().json;
// Use it to filter all items
const allItems = $input.all();
const matching = allItems.filter(item =>
item.json.category === firstData.targetCategory
);
return matching;Pattern 3: $input.item - Current Item (Each Item Mode)
Usage: Common in "Run Once for Each Item" mode
When to use:
Mode is set to "Run Once for Each Item"
Need to process items independently
Per-item API calls or validations
Item-specific error handling
IMPORTANT: Only use in "Each Item" mode. Will be undefined in "All Items" mode.
Basic Usage
// In "Run Once for Each Item" mode
const currentItem = $input.item;
const data = currentItem.json;
console.log('Processing item:', data.id);
return [{
json: {
...data,
processed: true
}
}];Example 1: Add Processing Metadata
const item = $input.item;
return [{
json: {
...item.json,
processed: true,
processedAt: new Date().toISOString(),
processingDuration: Math.random() * 1000 // Simulated duration
}
}];Example 2: Per-Item Validation
const item = $input.item;
const data = item.json;
// Validate this specific item
const errors = [];
if (!data.email) errors.push('Email required');
if (!data.name) errors.push('Name required');
if (data.age && data.age < 18) errors.push('Must be 18+');
return [{
json: {
...data,
valid: errors.length === 0,
errors: errors.length > 0 ? errors : undefined
}
}];Example 3: Item-Specific API Call
const item = $input.item;
const userId = item.json.userId;
// Make API call specific to this item
const response = await this.helpers.httpRequest({
method: 'GET',
url: `https://api.example.com/users/${userId}/details`
});
return [{
json: {
...item.json,
details: response
}
}];⚠️ Use
this.helpers.httpRequest, not$helpers. In the Code node's task-runner sandbox (default since n8n v2.0) the bare$helpersglobal is undefined —$helpers.httpRequest()throwsReferenceError: $helpers is not defined. For authenticated APIs, don't extend this pattern.this.helpers.httpRequestWithAuthenticationis blocked in the task-runner sandbox. Use an HTTP Request node with the credential attached, or delegate to a sub-workflow whose HTTP Request node holds the credential. For anything beyond a trivial unauthenticated GET, prefer the HTTP Request node anyway. See ERROR_PATTERNS.md Error #6.
Example 4: Conditional Processing
const item = $input.item;
const data = item.json;
// Process based on item type
if (data.type === 'premium') {
return [{
json: {
...data,
discount: 0.20,
tier: 'premium'
}
}];
} else {
return [{
json: {
...data,
discount: 0.05,
tier: 'standard'
}
}];
}Pattern 4: $node - Reference Other Nodes
Usage: Less common, but powerful for specific scenarios
When to use:
Need data from specific named node
Combining data from multiple nodes
Accessing metadata about workflow execution
Basic Usage
// Get output from specific node
const webhookData = $node["Webhook"].json;
const apiData = $node["HTTP Request"].json;
return [{
json: {
fromWebhook: webhookData,
fromAPI: apiData
}
}];Example 1: Combine Multiple Sources
// Reference multiple nodes
const webhook = $node["Webhook"].json;
const database = $node["Postgres"].json;
const api = $node["HTTP Request"].json;
return [{
json: {
combined: {
webhook: webhook.body,
dbRecords: database.length,
apiResponse: api.status
},
processedAt: new Date().toISOString()
}
}];Example 2: Compare Across Nodes
const oldData = $node["Get Old Data"].json;
const newData = $node["Get New Data"].json;
// Compare
const changes = {
added: newData.filter(n => !oldData.find(o => o.id === n.id)),
removed: oldData.filter(o => !newData.find(n => n.id === o.id)),
modified: newData.filter(n => {
const old = oldData.find(o => o.id === n.id);
return old && JSON.stringify(old) !== JSON.stringify(n);
})
};
return [{
json: {
changes,
summary: {
added: changes.added.length,
removed: changes.removed.length,
modified: changes.modified.length
}
}
}];Example 3: Access Node Metadata
// Get data from specific execution path
const ifTrueBranch = $node["IF True"].json;
const ifFalseBranch = $node["IF False"].json;
// Use whichever branch executed
const result = ifTrueBranch || ifFalseBranch || {};
return [{json: result}];Critical: Webhook Data Structure
MOST COMMON MISTAKE: Forgetting webhook data is nested under .body
The Problem
Webhook node wraps all incoming data under a body property. This catches many developers by surprise.
Structure
// Webhook node output structure:
{
"headers": {
"content-type": "application/json",
"user-agent": "...",
// ... other headers
},
"params": {},
"query": {},
"body": {
// ← YOUR DATA IS HERE
"name": "Alice",
"email": "[email protected]",
"message": "Hello!"
}
}Wrong vs Right
// ❌ WRONG: Trying to access directly
const name = $json.name; // undefined
const email = $json.email; // undefined
// ✅ CORRECT: Access via .body
const name = $json.body.name; // "Alice"
const email = $json.body.email; // "[email protected]"
// ✅ CORRECT: Extract body first
const webhookData = $json.body;
const name = webhookData.name; // "Alice"
const email = webhookData.email; // "[email protected]"Example: Full Webhook Processing
// Get webhook data from previous node
const webhookOutput = $input.first().json;
// Access the actual payload
const payload = webhookOutput.body;
// Access headers if needed
const contentType = webhookOutput.headers['content-type'];
// Access query parameters if needed
const apiKey = webhookOutput.query.api_key;
// Process the actual data
return [{
json: {
// Data from webhook body
userName: payload.name,
userEmail: payload.email,
message: payload.message,
// Metadata
receivedAt: new Date().toISOString(),
contentType: contentType,
authenticated: !!apiKey
}
}];POST Data, Query Params, and Headers
const webhook = $input.first().json;
return [{
json: {
// POST body data
formData: webhook.body,
// Query parameters (?key=value)
queryParams: webhook.query,
// HTTP headers
userAgent: webhook.headers['user-agent'],
contentType: webhook.headers['content-type'],
// Request metadata
method: webhook.method, // POST, GET, etc.
url: webhook.url
}
}];Common Webhook Scenarios
// Scenario 1: Form submission
const formData = $json.body;
const name = formData.name;
const email = formData.email;
// Scenario 2: JSON API webhook
const apiPayload = $json.body;
const eventType = apiPayload.event;
const data = apiPayload.data;
// Scenario 3: Query parameters
const apiKey = $json.query.api_key;
const userId = $json.query.user_id;
// Scenario 4: Headers
const authorization = $json.headers['authorization'];
const signature = $json.headers['x-signature'];Choosing the Right Pattern
Decision Tree
Do you need ALL items from previous node?
├─ YES → Use $input.all()
│
└─ NO → Do you need just the FIRST item?
├─ YES → Use $input.first()
│
└─ NO → Are you in "Each Item" mode?
├─ YES → Use $input.item
│
└─ NO → Do you need specific node data?
├─ YES → Use $node["NodeName"]
└─ NO → Use $input.first() (default)Quick Reference Table
| Scenario | Use This | Example |
|---|---|---|
| Sum all amounts | $input.all() | allItems.reduce((sum, i) => sum + i.json.amount, 0) |
| Get API response | $input.first() | $input.first().json.data |
| Process each independently | $input.item | $input.item.json (Each Item mode) |
| Combine two nodes | $node["Name"] | $node["API"].json |
| Filter array | $input.all() | allItems.filter(i => i.json.active) |
| Transform single object | $input.first() | {...input.first().json, new: true} |
| Webhook data | $input.first() | $input.first().json.body |
Common Mistakes
Mistake 1: Using $json Without Context
// ❌ WRONG: $json is ambiguous
const value = $json.field;
// ✅ CORRECT: Be explicit
const value = $input.first().json.field;Mistake 2: Forgetting .json Property
// ❌ WRONG: Trying to access fields on item object
const items = $input.all();
const names = items.map(item => item.name); // undefined
// ✅ CORRECT: Access via .json
const names = items.map(item => item.json.name);Mistake 3: Using $input.item in All Items Mode
// ❌ WRONG: $input.item is undefined in "All Items" mode
const data = $input.item.json; // Error!
// ✅ CORRECT: Use appropriate method
const data = $input.first().json; // Or $input.all()Mistake 4: Not Handling Empty Arrays
// ❌ WRONG: Crashes if no items
const first = $input.all()[0].json;
// ✅ CORRECT: Check length first
const items = $input.all();
if (items.length === 0) {
return [];
}
const first = items[0].json;
// ✅ ALSO CORRECT: Use $input.first()
const first = $input.first().json; // Built-in safetyMistake 5: Modifying Original Data
// ❌ RISKY: Mutating original
const items = $input.all();
items[0].json.modified = true; // Modifies original
return items;
// ✅ SAFE: Create new objects
const items = $input.all();
return items.map(item => ({
json: {
...item.json,
modified: true
}
}));Advanced Patterns
Pattern: Pagination Handling
Don't accumulate pages in Code. HTTP Request's pagination option fetches every page and returns the combined items
(page-number, offset, cursor and next-URL APIs, with a page cap): see n8n-node-configuration → NATIVE_NODES.md →
"HTTP Request pagination". Paging in Code is only for an API whose next-page rule the option can't express, and then it
is a loop exception with a Why Code: note.
Pattern: Conditional Node Reference
// Access different nodes based on condition
const condition = $input.first().json.type;
let data;
if (condition === 'api') {
data = $node["API Response"].json;
} else if (condition === 'database') {
data = $node["Database"].json;
} else {
data = $node["Default"].json;
}
return [{json: data}];Pattern: Multi-Node Aggregation
Native first: a Merge node in append mode (set numberInputs to the branch count) collects branch outputs without
code. Reach for this pattern only when the sources are optional or conditional and a Merge would wait on branches that
never run.
// Collect data from multiple named nodes
const sources = ['Source1', 'Source2', 'Source3'];
const allData = [];
for (const source of sources) {
const nodeData = $node[source]?.json;
if (nodeData) {
allData.push({
source,
data: nodeData
});
}
}
return allData.map(item => ({json: item}));Summary
Most Common Patterns:
$input.all()- Process multiple items, batch operations$input.first()- Single item, API responses$input.item- Each Item mode processing
Critical Rule:
Webhook data is under
.bodyproperty
Best Practice:
Be explicit: Use
$input.first().json.fieldinstead of$json.fieldAlways check for null/undefined
Use appropriate method for your mode (All Items vs Each Item)
See Also:
SKILL.md - Overview and quick start
COMMON_PATTERNS.md - Production patterns
ERROR_PATTERNS.md - Avoid common mistakes
Mode Performance: Why "All Items" Is Faster
Mode choice is the single biggest performance lever in a Code node, and the reason generalizes to the rest of your workflow. Every time n8n hands items to a per-item execution context it pays a setup cost. Measured on an n8n 2.x instance (small records, ~10k items):
| What runs per item | Approx. cost | Why |
|---|---|---|
| Code All Items (one run for the whole set) | ~0.02 ms/item | one context setup, then plain JS — the loop is free |
| Expression in any node (IF / Set / etc.) | ~0.2 ms/item | a light eval context per item |
| Code Each Item | ~0.6 ms/item | a full code sandbox per item — ~3× an expression, ~25–30× All Items |
So Run Once for Each Item over 10k items is ~6 s of pure overhead vs ~0.2 s for the same logic 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.
Two corollaries you will hit constantly:
Expression complexity is essentially free. An elaborate
{{ }}measures the same as a trivial one — ~90% of the cost is n8n building the per-item context, not running your code. Don't simplify expressions for speed; reduce the number of per-item boundaries instead.Every node→node hop re-copies all items (~0.05 ms/item per hop). Six chained All Items Code nodes cost ~7× a single node doing the same six steps. On a measured hot path (thousands of items, transform-bound) that makes one All Items node the heavy all-items exception, recorded in its
Why Code:note; below that, native nodes win on readability. Never build a chain of Each Item Code nodes, where the per-item tax multiplies by node count (a 6-node Each-Item chain over 2k items ≈ 7 s).
Scale check: below a few hundred items this is all sub-100 ms and not worth a thought, and most real workflows are dominated by I/O (HTTP / DB / Sheets round-trips) that dwarfs node overhead. Reach for these rules on the hot path — large item counts with little I/O — not everywhere.
Production Gotchas
Hard-won lessons from real-world n8n workflow deployments.
SplitInBatches Loop Semantics
The SplitInBatches node has two outputs — and the naming is counterintuitive:
main[0]= done — fires ONCE after all batches are processedmain[1]= each batch — fires for every batch (this is the loop body)
Always add a Limit 1 node after the done output before downstream processing, as a safety against edge cases where done fires with extra items.
SplitInBatches: Iteration Count Is the Cost
Each loop iteration re-executes the entire loop body through the workflow engine — ~0.8 ms per iteration of pure overhead, on top of whatever the body does. Total ≈ ⌈items / batchSize⌉ × (~0.8 ms + body cost):
batchSize: 1over N items pays that N times — it's the loop equivalent of Run Once for Each Item (and if the body has several nodes, each iteration re-pays all of them).Raising
batchSizecuts iterations proportionally; the body still sees every item. Use the largest batch your real constraint allows (API rate limit, page size, memory). If you don't need batching at all, don't loop — process the whole set in one All Items node.
Cross-Iteration Data Accumulation (CRITICAL)
After a SplitInBatches loop, $('Node Inside Loop').all() returns ONLY the last iteration's items, not cumulative results. This silently drops data from all but the final batch.
Fix: Use workflow static data to accumulate across iterations:
// BEFORE the loop (reset accumulator):
const staticData = $getWorkflowStaticData('global');
staticData.results = [];
return $input.all();
// INSIDE the loop body (accumulate):
const staticData = $getWorkflowStaticData('global');
const results = [];
for (const item of $input.all()) {
const processed = { /* ... */ };
results.push({ json: processed });
staticData.results.push(processed);
}
return results;
// AFTER the loop (read accumulated data):
const staticData = $getWorkflowStaticData('global');
const allResults = staticData.results || [];
// Now aggregate across ALL iterationspairedItem for New Output Items
When creating new items that don't map 1:1 to input items, include pairedItem — otherwise downstream Set nodes fail with paired_item_no_info:
const results = [];
for (let i = 0; i < $input.all().length; i++) {
const item = $input.all()[i];
results.push({
json: { /* new data */ },
pairedItem: { item: i }
});
}
return results;Correct Node Reference Syntax
// ❌ WRONG - .json directly on node reference
const data = $('HTTP Request').json;
// ✅ CORRECT - call .first() then access .json
const data = $('HTTP Request').first().json;
// ✅ Also correct - get all items
const allData = $('HTTP Request').all();Float Precision for Price/Currency Comparison
When comparing prices or currency values, floating point noise can cause false positives. Round to cents:
// ❌ Unreliable - float comparison
if (newPrice !== oldPrice) { /* triggers on noise */ }
// ✅ Reliable - compare at cent level
if (Math.round(newPrice * 100) !== Math.round(oldPrice * 100)) {
// Real price change detected
}Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Node.js
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Romuald Czlonkowski
- Publisher
- Romuald Czlonkowski (Community)
- Source
- czlonkowski/n8n-skills · skills/n8n-code-javascript · commit e037563
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Romuald Czlonkowski or Romuald Czlonkowski.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download n8n-code-javascript.zip