n8n-code-javascript

Write and debug JavaScript code in n8n Code nodes with proper data access and return formatting. Supports two execution modes: "Run Once for All Items" (recommended for 95% of use cases) and "Run Once for Each Item" for specialized per-item logic Access data via $input.all() , $input.first() , or $input.item ; always return array format [{json: {...}}] Built-in helpers include $helpers.httpRequest() for HTTP calls, DateTime (Luxon) for date operations, and $jmespath() for JSON querying Critical gotcha: webhook data is nested under .body property, not at root level; common mistakes include missing return statements, incorrect return format, and expression syntax confusion

INSTALLATION
npx skills add https://github.com/czlonkowski/n8n-skills --skill n8n-code-javascript
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

JavaScript Code Node

Expert guidance for writing JavaScript code in n8n Code nodes.

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

1. **Choose "Run Once for All Items" mode** (recommended for most use cases)

2. **Access data**: `$input.all()`, `$input.first()`, or `$input.item`

3. **CRITICAL**: Must return `[{json: {...}}]` format

4. **CRITICAL**: Webhook data is under `$json.body` (not `$json` directly)

5. **Built-ins available**: $helpers.httpRequest() (no auth), DateTime (Luxon), $jmespath(). **Not available**: $helpers.httpRequestWithAuthentication, $env (when N8N_BLOCK_ENV_ACCESS_IN_NODE=true), require() (unless allowlisted)

---

## 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**: Aggregation, filtering, batch processing, transformations, API calls with all data

- **Performance**: Faster for multiple items (single execution)

// Example: Calculate total from all items

const allItems = $input.all();

const total = allItems.reduce((sum, item) => sum + (item.json.amount || 0), 0);

return [{

json: {

total,

count: allItems.length,

average: total / allItems.length

}

}];


**When to use:**

- ✅ Comparing items across the dataset

- ✅ Calculating totals, averages, or statistics

- ✅ Sorting or ranking items

- ✅ Deduplication

- ✅ Building aggregated reports

- ✅ Combining data from multiple items

### 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)

// 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)

## Data Access Patterns

### Pattern 1: $input.all() - Most Common

**Use when**: Processing arrays, batch operations, aggregations

// Get all items from previous node

const allItems = $input.all();

// Filter, map, reduce as needed

const valid = allItems.filter(item => item.json.status === 'active');

const mapped = valid.map(item => ({

json: {

id: item.json.id,

name: item.json.name

}

}));

return mapped;


### Pattern 2: $input.first() - Very Common

**Use when**: Working with single objects, API responses, first-in-first-out

// Get first item only

const firstItem = $input.first();

const data = firstItem.json;

return [{

json: {

result: processData(data),

processedAt: new Date().toISOString()

}

}];


### Pattern 3: $input.item - Each Item Mode Only

**Use when**: In "Run Once for Each Item" mode

// Current item in loop (Each Item mode only)

const currentItem = $input.item;

return [{

json: {

...currentItem.json,

itemProcessed: true

}

}];


### Pattern 4: $node - Reference Other Nodes

**Use when**: Need data from specific nodes in workflow

// Get output from specific node

const webhookData = $node["Webhook"].json;

const httpData = $node["HTTP Request"].json;

return [{

json: {

combined: {

webhook: webhookData,

api: httpData

}

}

}];


**See**: [DATA_ACCESS.md](https://github.com/czlonkowski/n8n-skills/blob/HEAD/skills/n8n-code-javascript/DATA_ACCESS.md) for comprehensive guide

## 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](https://github.com/czlonkowski/n8n-skills/blob/HEAD/skills/n8n-code-javascript/DATA_ACCESS.md) for full webhook structure details

## Return Format Requirements

**CRITICAL RULE**: Always return array of objects with `json` property

### 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 [];

}


### Incorrect Return Formats

// ❌ WRONG: Object without array wrapper

return {

json: {field: value}

};

// ❌ WRONG: Array without json wrapper

return [{field: value}];

// ❌ WRONG: Plain string

return "processed";

// ❌ WRONG: Raw data without mapping

return $input.all(); // Missing .map()

// ❌ WRONG: Incomplete structure

return [{data: value}]; // Should be {json: value}


**Why it matters**: Next nodes expect array format. Incorrect format causes workflow execution to fail.

**See**: [ERROR_PATTERNS.md](https://github.com/czlonkowski/n8n-skills/blob/HEAD/skills/n8n-code-javascript/ERROR_PATTERNS.md) #3 for detailed error solutions

## Common Patterns Overview

Based on production workflows, here are the most useful patterns:

### 1. Multi-Source Data Aggregation

Combine data from multiple APIs, webhooks, or nodes

const allItems = $input.all();

const results = [];

for (const item of allItems) {

const sourceName = item.json.name || 'Unknown';

// Parse source-specific structure

if (sourceName === 'API1' && item.json.data) {

results.push({

json: {

title: item.json.data.title,

source: 'API1'

}

});

}

}

return results;


### 2. Filtering with Regex

Extract patterns, mentions, or keywords from text

const pattern = /\b([A-Z]{2,5})\b/g;

const matches = {};

for (const item of $input.all()) {

const text = item.json.text;

const found = text.match(pattern);

if (found) {

found.forEach(match => {

matches[match] = (matches[match] || 0) + 1;

});

}

}

return [{json: {matches}}];


### 3. Data Transformation & Enrichment

Map fields, normalize formats, add computed fields

const items = $input.all();

return items.map(item => {

const data = item.json;

const nameParts = data.name.split(' ');

return {

json: {

first_name: nameParts[0],

last_name: nameParts.slice(1).join(' '),

email: data.email,

created_at: new Date().toISOString()

}

};

});


### 4. Top N Filtering & Ranking

Sort and limit results

const items = $input.all();

const topItems = items

.sort((a, b) => (b.json.score || 0) - (a.json.score || 0))

.slice(0, 10);

return topItems.map(item => ({json: item.json}));


### 5. Aggregation & Reporting

Sum, count, group data

const items = $input.all();

const total = items.reduce((sum, item) => sum + (item.json.amount || 0), 0);

return [{

json: {

total,

count: items.length,

average: total / items.length,

timestamp: new Date().toISOString()

}

}];


**See**: [COMMON_PATTERNS.md](https://github.com/czlonkowski/n8n-skills/blob/HEAD/skills/n8n-code-javascript/COMMON_PATTERNS.md) for 10 detailed production patterns

## Error Prevention - Top 5 Mistakes

### #1: Empty Code or Missing Return (Most Common)

// ❌ WRONG: No return statement

const items = $input.all();

// ... processing code ...

// Forgot to return!

// ✅ CORRECT: Always return data

const items = $input.all();

// ... processing ...

return items.map(item => ({json: item.json}));


### #2: Expression Syntax Confusion

// ❌ WRONG: Using n8n expression syntax in code

const value = "{{ $json.field }}";

// ✅ CORRECT: Use JavaScript template literals

const value = ${$json.field};

// ✅ CORRECT: Direct access

const value = $input.first().json.field;


### #3: Incorrect Return Wrapper

// ❌ WRONG: Returning object instead of array

return {json: {result: 'success'}};

// ✅ CORRECT: Array wrapper required

return [{json: {result: 'success'}}];


### #4: Missing Null Checks

// ❌ WRONG: Crashes if field doesn't exist

const value = item.json.user.email;

// ✅ CORRECT: Safe access with optional chaining

const value = item.json?.user?.email || 'no-email@example.com';

// ✅ CORRECT: Guard clause

if (!item.json.user) {

return [];

}

const value = item.json.user.email;


### #5: Webhook Body Nesting

// ❌ WRONG: Direct access to webhook data

const email = $json.email;

// ✅ CORRECT: Webhook data under .body

const email = $json.body.email;


**See**: [ERROR_PATTERNS.md](https://github.com/czlonkowski/n8n-skills/blob/HEAD/skills/n8n-code-javascript/ERROR_PATTERNS.md) for comprehensive error guide

## Built-in Functions & Helpers

### $helpers.httpRequest()

Make HTTP requests from within code:

const response = await $helpers.httpRequest({

method: 'GET',

url: 'https://api.example.com/data',

headers: {

'Authorization': 'Bearer token',

'Content-Type': 'application/json'

}

});

return [{json: {data: response}}];


### DateTime (Luxon)

Date and time operations:

// Current time

const now = DateTime.now();

// Format dates

const formatted = now.toFormat('yyyy-MM-dd');

const iso = now.toISO();

// Date arithmetic

const tomorrow = now.plus({days: 1});

const lastWeek = now.minus({weeks: 1});

return [{

json: {

today: formatted,

tomorrow: tomorrow.toFormat('yyyy-MM-dd')

}

}];


### $jmespath()

Query JSON structures:

const data = $input.first().json;

// Filter array

const adults = $jmespath(data, 'users[?age >= 18]');

// Extract fields

const names = $jmespath(data, 'users[*].name');

return [{json: {adults, names}}];


**See**: [BUILTIN_FUNCTIONS.md](https://github.com/czlonkowski/n8n-skills/blob/HEAD/skills/n8n-code-javascript/BUILTIN_FUNCTIONS.md) for complete reference

## Best Practices

### 1. Always Validate Input Data

const items = $input.all();

// Check if data exists

if (!items || items.length === 0) {

return [];

}

// Validate structure

if (!items[0].json) {

return [{json: {error: 'Invalid input format'}}];

}

// Continue processing...


### 2. Use Try-Catch for Error Handling

try {

const response = await $helpers.httpRequest({

url: 'https://api.example.com/data'

});

return [{json: {success: true, data: response}}];

} catch (error) {

return [{

json: {

success: false,

error: error.message

}

}];

}


### 3. Prefer Array Methods Over Loops

// ✅ GOOD: Functional approach

const processed = $input.all()

.filter(item => item.json.valid)

.map(item => ({json: {id: item.json.id}}));

// ❌ SLOWER: Manual loop

const processed = [];

for (const item of $input.all()) {

if (item.json.valid) {

processed.push({json: {id: item.json.id}});

}

}


### 4. Filter Early, Process Late

// ✅ GOOD: Filter first to reduce processing

const processed = $input.all()

.filter(item => item.json.status === 'active') // Reduce dataset first

.map(item => expensiveTransformation(item)); // Then transform

// ❌ WASTEFUL: Transform everything, then filter

const processed = $input.all()

.map(item => expensiveTransformation(item)) // Wastes CPU

.filter(item => item.json.status === 'active');


### 5. Use Descriptive Variable Names

// ✅ GOOD: Clear intent

const activeUsers = $input.all().filter(item => item.json.active);

const totalRevenue = activeUsers.reduce((sum, user) => sum + user.json.revenue, 0);

// ❌ BAD: Unclear purpose

const a = $input.all().filter(item => item.json.active);

const t = a.reduce((s, u) => s + u.json.revenue, 0);


### 6. Debug with console.log()

// Debug statements appear in browser console

const items = $input.all();

console.log(Processing ${items.length} items);

for (const item of items) {

console.log('Item data:', item.json);

// Process...

}

return result;


## 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 processed

- `main[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.

### 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 iterations


### pairedItem 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

}

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card