All skills
wilkomarketing avatar

/n8n-code-javascript

@c62bd9f

Write JavaScript code in n8n Code nodes. Use when writing JavaScript in n8n, using $input/$json/$node syntax, making HTTP requests with this.helpers / the $helpers global, working with dates using DateTime, troubleshooting Code node errors, choosing between Code node modes, or doing any custom data transformation in n8n. Always use this skill when a workflow needs a Code node — whether for data aggregation, filtering, API calls, format conversion, batch processing logic, or any custom JavaScript. Covers SplitInBatches loop patterns, cross-iteration data, pairedItem, and real-world production patterns. Also use when asked why a Code node or workflow is slow, which execution mode is faster, or how to cut per-item overhead on large datasets. EXCEPTION — for the AI-agent-callable Custom Code Tool (@n8n/n8n-nodes-langchain.toolCode, a tool attached to an AI Agent), use the n8n-code-tool skill instead; it has a different runtime contract.

Use this Skill: https://skilld.dev/gh/wilkomarketing/antigravity-n8n-skills/n8n-code-javascript

This session only. Nothing lands on disk.

DATA_ACCESS.md

≈5.5k tokens on demand. Your agent reads this file only when SKILL.md points to it.

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

  1. $input.all() - Most common - Batch operations, aggregations
  2. $input.first() - Very common - Single item operations
  3. $input.item - Common - Each Item mode only
  4. $node["NodeName"].json - Specific node references
  5. $json - Direct current item (legacy, use $input instead)

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 $helpers global is undefined — $helpers.httpRequest() throws ReferenceError: $helpers is not defined. For authenticated APIs, don't extend this pattern. this.helpers.httpRequestWithAuthentication is 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": "alice@example.com",
    "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;  // "alice@example.com"

// ✅ CORRECT: Extract body first
const webhookData = $json.body;
const name = webhookData.name;  // "Alice"
const email = webhookData.email;  // "alice@example.com"

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 safety

Mistake 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

const currentPage = $input.all();
const pageNumber = $node["Set Page"].json.page || 1;

// Combine with previous pages
const allPreviousPages = $node["Accumulator"]?.json.accumulated || [];

return [{
  json: {
    accumulated: [...allPreviousPages, ...currentPage],
    currentPage: pageNumber,
    totalItems: allPreviousPages.length + currentPage.length
  }
}];

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

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

  1. $input.all() - Process multiple items, batch operations
  2. $input.first() - Single item, API responses
  3. $input.item - Each Item mode processing

Critical Rule:

  • Webhook data is under .body property

Best Practice:

  • Be explicit: Use $input.first().json.field instead of $json.field
  • Always check for null/undefined
  • Use appropriate method for your mode (All Items vs Each Item)

See Also:


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, so consolidate a hot transform chain into one All Items node — and 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 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.

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: 1 over 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 batchSize cuts 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 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
}

Source: SKILL.md on GitHub

No alerts11d3 checks · Risk SAFE
  • Gen Agent Trust Hub11d

    The skill provides comprehensive documentation and best practices for writing JavaScript in n8n Code nodes. It includes guidance on data access, error prevention, and production-tested patterns while explicitly warning against insecure coding practices like hardcoding secrets or direct environment variable access.

  • Socket11d

    No alerts

  • Snyk11d

    Risk: LOW · No issues

Signed by skilld at c62bd9f. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated 3 months ago

README badge

README badge for wilkomarketing/antigravity-n8n-skills/n8n-code-javascript