All skills
czlonkowski avatar

/n8n-code-javascript

@b90ba12

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/czlonkowski/n8n-skills/n8n-code-javascript

This session only. Nothing lands on disk.

BUILTIN_FUNCTIONS.md

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

Built-in Functions - JavaScript Code Node

Complete reference for n8n's built-in JavaScript functions and helpers.


Overview

n8n Code nodes provide powerful built-in functions beyond standard JavaScript. This guide covers:

  1. Sandbox restrictions - What's blocked and why (READ FIRST)
  2. this.helpers.httpRequest() - Make HTTP requests (the bare $helpers global is undefined in the task runner)
  3. DateTime (Luxon) - Advanced date/time operations
  4. $jmespath() - Query JSON structures
  5. $getWorkflowStaticData() - Persistent storage
  6. Standard JavaScript Globals - Math, JSON, console, etc.
  7. Available Node.js Modules - crypto, Buffer, URL

0. Sandbox Restrictions (Critical)

Since n8n v2.0, Code nodes execute inside a task runner sandbox (JsTaskRunnerSandbox) which deliberately blocks several APIs. The legacy vm2 sandbox is being removed. Knowing what's blocked saves hours of "why does this throw on activation but not in the editor preview."

Blocked helpers

// ❌ BLOCKED — throws UnsupportedFunctionError
await this.helpers.httpRequestWithAuthentication.call(this, 'credType', { ... });
await this.helpers.requestWithAuthenticationPaginated.call(this, { ... }, 'credType');

n8n's source comment explains why: "these rely on checking the credentials from the current node type (Code Node), and Code Node doesn't have credentials." There is no env var to re-enable them in the task runner — the deny-list is compiled-in (packages/@n8n/task-runner/src/runner-types.ts).

Workaround: don't try to authenticate from inside a Code node. Instead, either:

  • Replace the Code node with an HTTP Request node that has the credential attached (the canonical pattern), or
  • Have the Code node prepare a payload and delegate to a sub-workflow whose HTTP Request node holds the credential.

$env may be blocked

$env is gated by N8N_BLOCK_ENV_ACCESS_IN_NODE. When set to true (a common production hardening), any reference to $env.SOMETHING throws. Since you can't tell from inside the Code node whether it's enabled, don't rely on $env for portable skills — treat secrets as a credential concern (HTTP Request node) rather than a Code-node concern.

require() is gated by allowlists

// May throw "Cannot find module 'crypto'" — depends on env vars
const crypto = require('crypto');

Built-in modules need N8N_RUNNERS_ALLOWED_BUILT_IN_MODULES (or legacy NODE_FUNCTION_ALLOW_BUILTIN) set to * or a comma-list including crypto. External npm packages need N8N_RUNNERS_ALLOWED_EXTERNAL_MODULES plus the package being installed in the runner image. On default installs neither is set — require() throws.

Buffer and URL are globals (not require'd), so they always work.

What's always safe

$input.*, $json, $node[…], this.helpers.httpRequest() (without auth), $jmespath(), $getWorkflowStaticData(), DateTime (Luxon), and all standard JavaScript globals (Math, JSON, Object, Array, console, Buffer, URL, URLSearchParams).

Accessor gotcha: the bare $helpers global is undefined in the task-runner sandbox — $helpers.httpRequest() throws ReferenceError: $helpers is not defined. The working accessor is this.helpers.httpRequest() (inside a nested async function where this is lost, call it as await fn.call(this, ...)). The n8n-mcp validator may wrongly suggest $helpers — ignore it. And for anything beyond a trivial unauthenticated GET (pagination, retries, credentials), prefer the HTTP Request node and keep Code nodes for pure logic.


1. this.helpers.httpRequest() - HTTP Requests

Make HTTP requests directly from Code nodes without using HTTP Request node. The accessor is this.helpers.httpRequest() — the bare $helpers global is undefined in the task-runner sandbox and throws ReferenceError: $helpers is not defined. For non-trivial calls (pagination, retries, credentials) prefer the HTTP Request node.

Basic Usage

const response = await this.helpers.httpRequest({
  method: 'GET',
  url: 'https://api.example.com/users'
});

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

Complete Options

const response = await this.helpers.httpRequest({
  method: 'POST',  // GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
  url: 'https://api.example.com/users',
  headers: {
    'Authorization': 'Bearer token123',
    'Content-Type': 'application/json',
    'User-Agent': 'n8n-workflow'
  },
  body: {
    name: 'John Doe',
    email: 'john@example.com'
  },
  qs: {  // Query string parameters
    page: 1,
    limit: 10
  },
  timeout: 10000,  // Milliseconds (default: no timeout)
  json: true,  // Auto-parse JSON response (default: true)
  simple: false,  // Don't throw on HTTP errors (default: true)
  resolveWithFullResponse: false  // Return only body (default: false)
});

GET Request

// Simple GET
const users = await this.helpers.httpRequest({
  method: 'GET',
  url: 'https://api.example.com/users'
});

return [{json: {users}}];
// GET with query parameters
const results = await this.helpers.httpRequest({
  method: 'GET',
  url: 'https://api.example.com/search',
  qs: {
    q: 'javascript',
    page: 1,
    per_page: 50
  }
});

return [{json: results}];

POST Request

// POST with JSON body
// NOTE: For authenticated APIs, prefer an HTTP Request node with a credential
// attached. Embedding the token in a Code node only works when (a) the token
// arrives as runtime data (e.g. from a previous node), or (b) you're sure
// $env access is enabled on this instance. See section 0.
const apiToken = $input.first().json.apiToken;  // passed in from a credential-aware upstream node

const newUser = await this.helpers.httpRequest({
  method: 'POST',
  url: 'https://api.example.com/users',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${apiToken}`
  },
  body: {
    name: $json.body.name,
    email: $json.body.email,
    role: 'user'
  }
});

return [{json: newUser}];

PUT/PATCH Request

// Update resource
const updated = await this.helpers.httpRequest({
  method: 'PATCH',
  url: `https://api.example.com/users/${userId}`,
  body: {
    name: 'Updated Name',
    status: 'active'
  }
});

return [{json: updated}];

DELETE Request

// Delete resource
await this.helpers.httpRequest({
  method: 'DELETE',
  url: `https://api.example.com/users/${userId}`,
  headers: {
    'Authorization': `Bearer ${apiToken}`  // token passed in from upstream node, not $env
  }
});

return [{json: {deleted: true, userId}}];

Authentication Patterns

Strong preference: don't authenticate from inside a Code node. Use an HTTP Request node with a credential attached, or delegate to a sub-workflow whose HTTP Request node holds the credential. The patterns below only apply when the token genuinely flows through the workflow as data.

// Bearer Token (token came from a previous node, not $env)
const response = await this.helpers.httpRequest({
  url: 'https://api.example.com/data',
  headers: {
    'Authorization': `Bearer ${$input.first().json.token}`
  }
});
// API Key in Header (key came from a previous node, not $env)
const response = await this.helpers.httpRequest({
  url: 'https://api.example.com/data',
  headers: {
    'X-API-Key': $input.first().json.apiKey
  }
});
// Basic Auth (manual)
const credentials = Buffer.from(`${username}:${password}`).toString('base64');

const response = await this.helpers.httpRequest({
  url: 'https://api.example.com/data',
  headers: {
    'Authorization': `Basic ${credentials}`
  }
});

Error Handling

// Handle HTTP errors gracefully
try {
  const response = await this.helpers.httpRequest({
    method: 'GET',
    url: 'https://api.example.com/users',
    simple: false  // Don't throw on 4xx/5xx
  });

  if (response.statusCode >= 200 && response.statusCode < 300) {
    return [{json: {success: true, data: response.body}}];
  } else {
    return [{
      json: {
        success: false,
        status: response.statusCode,
        error: response.body
      }
    }];
  }
} catch (error) {
  return [{
    json: {
      success: false,
      error: error.message
    }
  }];
}

Full Response Access

// Get full response including headers and status
const response = await this.helpers.httpRequest({
  url: 'https://api.example.com/data',
  resolveWithFullResponse: true
});

return [{
  json: {
    statusCode: response.statusCode,
    headers: response.headers,
    body: response.body,
    rateLimit: response.headers['x-ratelimit-remaining']
  }
}];

2. DateTime (Luxon) - Date & Time Operations

n8n includes Luxon for powerful date/time handling. Access via DateTime global.

Current Date/Time

// Current time
const now = DateTime.now();

// Current time in specific timezone
const nowTokyo = DateTime.now().setZone('Asia/Tokyo');

// Today at midnight
const today = DateTime.now().startOf('day');

return [{
  json: {
    iso: now.toISO(),  // "2025-01-20T15:30:00.000Z"
    formatted: now.toFormat('yyyy-MM-dd HH:mm:ss'),  // "2025-01-20 15:30:00"
    unix: now.toSeconds(),  // Unix timestamp
    millis: now.toMillis()  // Milliseconds since epoch
  }
}];

Formatting Dates

const now = DateTime.now();

return [{
  json: {
    isoFormat: now.toISO(),  // ISO 8601: "2025-01-20T15:30:00.000Z"
    sqlFormat: now.toSQL(),  // SQL: "2025-01-20 15:30:00.000"
    httpFormat: now.toHTTP(),  // HTTP: "Mon, 20 Jan 2025 15:30:00 GMT"

    // Custom formats
    dateOnly: now.toFormat('yyyy-MM-dd'),  // "2025-01-20"
    timeOnly: now.toFormat('HH:mm:ss'),  // "15:30:00"
    readable: now.toFormat('MMMM dd, yyyy'),  // "January 20, 2025"
    compact: now.toFormat('yyyyMMdd'),  // "20250120"
    withDay: now.toFormat('EEEE, MMMM dd, yyyy'),  // "Monday, January 20, 2025"
    custom: now.toFormat('dd/MM/yy HH:mm')  // "20/01/25 15:30"
  }
}];

Parsing Dates

// From ISO string
const dt1 = DateTime.fromISO('2025-01-20T15:30:00');

// From specific format
const dt2 = DateTime.fromFormat('01/20/2025', 'MM/dd/yyyy');

// From SQL
const dt3 = DateTime.fromSQL('2025-01-20 15:30:00');

// From Unix timestamp
const dt4 = DateTime.fromSeconds(1737384600);

// From milliseconds
const dt5 = DateTime.fromMillis(1737384600000);

return [{json: {parsed: dt1.toISO()}}];

Date Arithmetic

const now = DateTime.now();

return [{
  json: {
    // Adding time
    tomorrow: now.plus({days: 1}).toISO(),
    nextWeek: now.plus({weeks: 1}).toISO(),
    nextMonth: now.plus({months: 1}).toISO(),
    inTwoHours: now.plus({hours: 2}).toISO(),

    // Subtracting time
    yesterday: now.minus({days: 1}).toISO(),
    lastWeek: now.minus({weeks: 1}).toISO(),
    lastMonth: now.minus({months: 1}).toISO(),
    twoHoursAgo: now.minus({hours: 2}).toISO(),

    // Complex operations
    in90Days: now.plus({days: 90}).toFormat('yyyy-MM-dd'),
    in6Months: now.plus({months: 6}).toFormat('yyyy-MM-dd')
  }
}];

Time Comparisons

const now = DateTime.now();
const targetDate = DateTime.fromISO('2025-12-31');

return [{
  json: {
    // Comparisons
    isFuture: targetDate > now,
    isPast: targetDate < now,
    isEqual: targetDate.equals(now),

    // Differences
    daysUntil: targetDate.diff(now, 'days').days,
    hoursUntil: targetDate.diff(now, 'hours').hours,
    monthsUntil: targetDate.diff(now, 'months').months,

    // Detailed difference
    detailedDiff: targetDate.diff(now, ['months', 'days', 'hours']).toObject()
  }
}];

Timezone Operations

const now = DateTime.now();

return [{
  json: {
    // Current timezone
    local: now.toISO(),

    // Convert to different timezone
    tokyo: now.setZone('Asia/Tokyo').toISO(),
    newYork: now.setZone('America/New_York').toISO(),
    london: now.setZone('Europe/London').toISO(),
    utc: now.toUTC().toISO(),

    // Get timezone info
    timezone: now.zoneName,  // "America/Los_Angeles"
    offset: now.offset,  // Offset in minutes
    offsetFormatted: now.toFormat('ZZ')  // "+08:00"
  }
}];

Start/End of Period

const now = DateTime.now();

return [{
  json: {
    startOfDay: now.startOf('day').toISO(),
    endOfDay: now.endOf('day').toISO(),
    startOfWeek: now.startOf('week').toISO(),
    endOfWeek: now.endOf('week').toISO(),
    startOfMonth: now.startOf('month').toISO(),
    endOfMonth: now.endOf('month').toISO(),
    startOfYear: now.startOf('year').toISO(),
    endOfYear: now.endOf('year').toISO()
  }
}];

Weekday & Month Info

const now = DateTime.now();

return [{
  json: {
    // Day info
    weekday: now.weekday,  // 1 = Monday, 7 = Sunday
    weekdayShort: now.weekdayShort,  // "Mon"
    weekdayLong: now.weekdayLong,  // "Monday"
    isWeekend: now.weekday > 5,  // Saturday or Sunday

    // Month info
    month: now.month,  // 1-12
    monthShort: now.monthShort,  // "Jan"
    monthLong: now.monthLong,  // "January"

    // Year info
    year: now.year,  // 2025
    quarter: now.quarter,  // 1-4
    daysInMonth: now.daysInMonth  // 28-31
  }
}];

3. $jmespath() - JSON Querying

Query and transform JSON structures using JMESPath syntax.

Consider an expression first. $jmespath works the same inside {{ }}, so a query that only feeds one field rarely needs a Code node. See n8n-expression-syntax → $jmespath().

Rules that bite (verified on n8n 2.38)

  • Argument order is $jmespath(object, query), the reverse of JMESPath's own docs (search(query, data)). Reversed, or given a string/undefined as the object, it throws expected two arguments (Object, string) for this function.
  • String literals take single quotes (tier == 'premium'), so wrap the query in a JS double-quoted string. "premium" in double quotes inside the query is a field name and silently returns [].
  • Numbers and booleans take backticks (age >= `18`, inStock == `true`). A bare 18 is a parse error.
  • Use && || ! ==, not and / or / =.
  • Over $input.all() items, include the wrapper ([?json.age >= \18`].json.name) or map first: $jmespath($input.all().map(i => i.json), '[?age >= `18`].name')`.
  • Parse errors throw in a Code node, but the message comes out garbled by the task runner: Cannot assign to read only property 'name' of object 'Error: Invalid token (Number): "18"'. If you see that, check the query's literals first. (Inside {{ }} the same error silently yields null instead.)
  • Missing path → null; filter with no match → [].

Basic Queries

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

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

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

// Get specific index
const firstUser = $jmespath(data, 'users[0]');

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

Advanced Queries

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

// Sort and slice
const top5 = $jmespath(data, 'users | sort_by(@, &score) | reverse(@) | [0:5]');

// Extract nested fields
const emails = $jmespath(data, 'users[*].contact.email');

// Multi-field extraction
const simplified = $jmespath(data, 'users[*].{name: name, email: contact.email}');

// Conditional filtering
const premium = $jmespath(data, "users[?subscription.tier == 'premium']");

return [{json: {top5, emails, simplified, premium}}];

Common Patterns

// Pattern 1: Filter and project
const query1 = $jmespath(data, 'products[?price > `100`].{name: name, price: price}');

// Pattern 2: Aggregate functions
const query2 = $jmespath(data, 'sum(products[*].price)');
const query3 = $jmespath(data, 'max(products[*].price)');
const query4 = $jmespath(data, 'length(products)');

// Pattern 3: Nested filtering
const query5 = $jmespath(data, 'categories[*].products[?inStock == `true`]');

return [{json: {query1, query2, query3, query4, query5}}];

4. $getWorkflowStaticData() - Persistent Storage

Store data that persists across workflow executions.

Basic Usage

// Get static data storage
const staticData = $getWorkflowStaticData();

// Initialize counter if doesn't exist
if (!staticData.counter) {
  staticData.counter = 0;
}

// Increment counter
staticData.counter++;

return [{
  json: {
    executionCount: staticData.counter
  }
}];

Use Cases

// Use Case 1: Rate limiting
const staticData = $getWorkflowStaticData();
const now = Date.now();

if (!staticData.lastRun) {
  staticData.lastRun = now;
  staticData.runCount = 1;
} else {
  const timeSinceLastRun = now - staticData.lastRun;

  if (timeSinceLastRun < 60000) {  // Less than 1 minute
    return [{json: {error: 'Rate limit: wait 1 minute between runs'}}];
  }

  staticData.lastRun = now;
  staticData.runCount++;
}

return [{json: {allowed: true, totalRuns: staticData.runCount}}];
// Use Case 2: Tracking last processed ID
const staticData = $getWorkflowStaticData();
const currentItems = $input.all();

// Get last processed ID
const lastId = staticData.lastProcessedId || 0;

// Filter only new items
const newItems = currentItems.filter(item => item.json.id > lastId);

// Update last processed ID
if (newItems.length > 0) {
  staticData.lastProcessedId = Math.max(...newItems.map(item => item.json.id));
}

return newItems;
// Use Case 3: Accumulating results
const staticData = $getWorkflowStaticData();

if (!staticData.accumulated) {
  staticData.accumulated = [];
}

// Add current items to accumulated list
const currentData = $input.all().map(item => item.json);
staticData.accumulated.push(...currentData);

return [{
  json: {
    currentBatch: currentData.length,
    totalAccumulated: staticData.accumulated.length,
    allData: staticData.accumulated
  }
}];

5. Standard JavaScript Globals

Math Object

return [{
  json: {
    // Rounding
    rounded: Math.round(3.7),  // 4
    floor: Math.floor(3.7),  // 3
    ceil: Math.ceil(3.2),  // 4

    // Min/Max
    max: Math.max(1, 5, 3, 9, 2),  // 9
    min: Math.min(1, 5, 3, 9, 2),  // 1

    // Random
    random: Math.random(),  // 0-1
    randomInt: Math.floor(Math.random() * 100),  // 0-99

    // Other
    abs: Math.abs(-5),  // 5
    sqrt: Math.sqrt(16),  // 4
    pow: Math.pow(2, 3)  // 8
  }
}];

JSON Object

// Parse JSON string
const jsonString = '{"name": "John", "age": 30}';
const parsed = JSON.parse(jsonString);

// Stringify object
const obj = {name: "John", age: 30};
const stringified = JSON.stringify(obj);

// Pretty print
const pretty = JSON.stringify(obj, null, 2);

return [{json: {parsed, stringified, pretty}}];

console Object

// Debug logging (appears in browser console, press F12)
console.log('Processing items:', $input.all().length);
console.log('First item:', $input.first().json);

// Other console methods
console.error('Error message');
console.warn('Warning message');
console.info('Info message');

// Continues to return data
return [{json: {processed: true}}];

Object Methods

const obj = {name: "John", age: 30, city: "NYC"};

return [{
  json: {
    keys: Object.keys(obj),  // ["name", "age", "city"]
    values: Object.values(obj),  // ["John", 30, "NYC"]
    entries: Object.entries(obj),  // [["name", "John"], ...]

    // Check property
    hasName: 'name' in obj,  // true

    // Merge objects
    merged: Object.assign({}, obj, {country: "USA"})
  }
}];

Array Methods

const arr = [1, 2, 3, 4, 5];

return [{
  json: {
    mapped: arr.map(x => x * 2),  // [2, 4, 6, 8, 10]
    filtered: arr.filter(x => x > 2),  // [3, 4, 5]
    reduced: arr.reduce((sum, x) => sum + x, 0),  // 15
    some: arr.some(x => x > 3),  // true
    every: arr.every(x => x > 0),  // true
    find: arr.find(x => x > 3),  // 4
    includes: arr.includes(3),  // true
    joined: arr.join(', ')  // "1, 2, 3, 4, 5"
  }
}];

6. Available Node.js Modules

crypto Module

Gated: require('crypto') only works if N8N_RUNNERS_ALLOWED_BUILT_IN_MODULES (or legacy NODE_FUNCTION_ALLOW_BUILTIN) includes crypto (or is *). On default installs it throws "Cannot find module 'crypto'". For hashing you control, prefer doing it before reaching the Code node, or — if you must — verify your instance's config first.

const crypto = require('crypto');

// Hash functions
const hash = crypto.createHash('sha256')
  .update('my secret text')
  .digest('hex');

// MD5 hash
const md5 = crypto.createHash('md5')
  .update('my text')
  .digest('hex');

// Random values
const randomBytes = crypto.randomBytes(16).toString('hex');

return [{json: {hash, md5, randomBytes}}];

Buffer (built-in)

// Base64 encoding
const encoded = Buffer.from('Hello World').toString('base64');

// Base64 decoding
const decoded = Buffer.from(encoded, 'base64').toString();

// Hex encoding
const hex = Buffer.from('Hello').toString('hex');

return [{json: {encoded, decoded, hex}}];

URL / URLSearchParams

// Parse URL
const url = new URL('https://example.com/path?param1=value1&param2=value2');

// Build query string
const params = new URLSearchParams({
  search: 'query',
  page: 1,
  limit: 10
});

return [{
  json: {
    host: url.host,
    pathname: url.pathname,
    search: url.search,
    queryString: params.toString()  // "search=query&page=1&limit=10"
  }
}];

What's NOT Available

External npm packages are NOT available (unless explicitly allowlisted via N8N_RUNNERS_ALLOWED_EXTERNAL_MODULES and installed in the runner image — rare):

  • ❌ axios
  • ❌ lodash
  • ❌ moment (use DateTime/Luxon instead)
  • ❌ request
  • ❌ Any other npm package

Authentication helpers are blocked in the task runner sandbox (see section 0):

  • ❌ this.helpers.httpRequestWithAuthentication
  • ❌ this.helpers.requestWithAuthenticationPaginated

Conditionally blocked (depends on instance config):

  • ⚠️ $env.* — blocked when N8N_BLOCK_ENV_ACCESS_IN_NODE=true
  • ⚠️ require('crypto') / require('fs') / etc. — blocked unless N8N_RUNNERS_ALLOWED_BUILT_IN_MODULES includes them

Workarounds:

  • HTTP with auth → HTTP Request node with credential attached, or sub-workflow pattern
  • Secrets → arrive as data from an upstream HTTP Request / credential-aware node
  • Hashing/crypto → do it in a service the workflow calls, or get your instance config updated

Summary

Most Useful Built-ins:

  1. this.helpers.httpRequest() - API calls without HTTP Request node (the bare $helpers global is undefined)
  2. DateTime - Professional date/time handling
  3. $jmespath() - Complex JSON queries
  4. Math, JSON, Object, Array - Standard JavaScript utilities

Common Patterns:

  • API calls: Use this.helpers.httpRequest() (or, preferably, the HTTP Request node)
  • Date operations: Use DateTime (Luxon)
  • Data filtering: Use $jmespath() or JavaScript .filter()
  • Persistent data: Use $getWorkflowStaticData()
  • Hashing: Use crypto module

See Also:

Source: SKILL.md on GitHub

No alerts15d5 checks · Risk SAFE
  • Gen Agent Trust Hub15d

    The skill provides comprehensive and secure guidance for writing JavaScript in n8n Code nodes. It includes high-quality technical advice on avoiding common security pitfalls, such as hardcoding secrets or bypassing authentication controls, and emphasizes the use of platform-native security features like the credential system and task runner sandbox.

  • Socket15d

    No alerts

  • Snyk15d

    Risk: LOW · No issues

  • Runlayer7mo

    1/6 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

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

Last checked against GitHub 2 weeks ago.

Activeupdated 3 months ago
  • n8n
  • javascript
  • code-node
  • workflow
  • data-transformation
  • aggregation
  • api-integration
  • batch-processing

README badge

README badge for czlonkowski/n8n-skills/n8n-code-javascript

Writes JavaScript code for n8n Code nodes, covering data access patterns ($input, $json, $node), the critical return format requirement, built-in helpers like $helpers.httpRequest() and DateTime, and common production patterns like aggregation, filtering, and batch processing. Includes mode selection (Run Once for All Items vs. Each Item) and top error patterns specific to n8n's Code node runtime.

Generated from the current SKILL.md.

What's the difference between 'Run Once for All Items' and 'Run Once for Each Item' mode?
'Run Once for All Items' executes the code once and accesses all input data via $input.all(); use this for 95% of cases including aggregations and batch processing. 'Run Once for Each Item' executes separately for each input item via $input.item; use only when items must be processed independently.
Why does my webhook data return undefined?
Webhook data is nested under the .body property. Access it via $json.body.fieldName or $input.first().json.body, not directly from $json.
What format must I return from a Code node?
Always return an array of objects with a json property: [{json: {field: value}}]. Returning a plain object, string, or data without the json wrapper will cause execution to fail.
Can I use require() to import external modules like axios or lodash?
Only if your n8n instance has allowlisted them via N8N_RUNNERS_ALLOWED_EXTERNAL_MODULES. Built-in functions like $helpers.httpRequest(), DateTime (Luxon), and $jmespath() are always available without require().
Is this skill for the Custom Code Tool used with AI Agents?
No. This skill covers standard Code nodes in workflows. For the Custom Code Tool (@n8n/n8n-nodes-langchain.toolCode) attached to AI Agents, use the n8n-code-tool skill instead—it has a different runtime contract.

Generated from the current SKILL.md. These answers refresh after source changes.