Generated banner for N8n Code Javascript
Generated logo for N8n Code Javascript

N8n Code Javascript

Romuald CzlonkowskiCommunityCreated by Romuald Czlonkowski

  • Skill
  • Tool integration
  • Automation and integrations
  • MIT
  • No issues found

From the author

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

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

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/czlonkowski/n8n-code-javascript/download
  2. Unzip 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:

  1. Is there a native node for the job? Check the job-to-node table in using-n8n-mcp-skills: Filter, IF / Switch, Summarize, Aggregate, Split Out, Merge, Compare Datasets, Remove Duplicates, Sort + Limit, Date & Time, XML / HTML / Extract From File / Convert To File, HTTP Request (pagination, batching) and Crypto cover most reaches for Code. Their configs are in n8n-node-configuration → NATIVE_NODES.md.

  2. Would a short expression in that node do? One expression, no function body — the helper methods (.pluck(), .sum(), .unique(), $ifEmpty(), .diffTo()) are in n8n-expression-syntax and, on n8n-mcp 2.97.0+, in tools_documentation({topic: "expression_helpers"}). Several statements inside {{ }} count as code, so they belong here rather than in an Edit Fields field.

  3. 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
  1. Choose "Run Once for All Items" mode (recommended for most use cases)

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

  3. Return [{json: {...}}] — the canonical, mode-portable form. In Run Once for All Items mode n8n also auto-wraps a bare return {…} object, so that runs too; what genuinely fails is returning a primitive (string/number) or null.

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

  5. Built-ins available: this.helpers.httpRequest() (no auth — the bare $helpers global is undefined in the task-runner sandbox, so $helpers.httpRequest() throws ReferenceError: $helpers is not defined), DateTime (Luxon), $jmespath(). Not available: this.helpers.httpRequestWithAuthentication (deny-listed), $env (when N8N_BLOCK_ENV_ACCESS_IN_NODE=true), require() (unless allowlisted). For anything beyond a trivial unauthenticated GET (auth, pagination, retries), prefer the HTTP Request node and keep Code nodes for pure logic.

  6. Instance-allowlisted libraries: Self-hosted instances can allowlist modules via N8N_RUNNERS_ALLOWED_BUILT_IN_MODULES and N8N_RUNNERS_ALLOWED_EXTERNAL_MODULES (legacy: NODE_FUNCTION_ALLOW_BUILTIN / NODE_FUNCTION_ALLOW_EXTERNAL). If the user says their instance allows specific modules (e.g. axios, lodash, crypto), use them via require() — don't refuse. If unsure, ask or default to built-ins only.

  7. Wrong skill? If you're writing code for a Custom Code Tool attached to an AI Agent (@n8n/n8n-nodes-langchain.toolCode), stop — that node has a different contract (input via query, must return a string, no $input/$helpers). Use the n8n-code-tool skill.


Mode Selection Guide

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

Use this mode for: 95% of use cases

  • How it works: Code executes once regardless of input count

  • Data access: $input.all() or items array

  • Best for: logic that needs every item at once and passed the gate above

  • Performance: Faster for multiple items (single execution)

// Example: running balance — each item depends on the ones before it,
// which no native node expresses (Summarize gives one total per group, not a running one)
let balance = 0;
return $input.all().map(item => {
  balance += item.json.amount || 0;
  return { json: { ...item.json, balance } };
});

When to use:

  • ✅ Each item depends on earlier items (running totals, sessionizing events, gap detection)

  • ✅ Multi-pass algorithms or recursion over nested data of unknown depth

  • ✅ Heavy transforms over thousands of items where one node beats a long chain (measured)

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

Run Once for Each Item

Use this mode for: Specialized cases only

  • How it works: Code executes separately for each input item

  • Data access: $input.item or $item

  • Best for: Item-specific logic, independent operations, per-item validation

  • Performance: Slower for large datasets (multiple executions)

// Example: Add processing timestamp to each item
const item = $input.item;

return [{
  json: {
    ...item.json,
    processed: true,
    processedAt: new Date().toISOString()
  }
}];

When to use:

  • ✅ Each item needs independent API call

  • ✅ Per-item validation with different error handling

  • ✅ Item-specific transformations based on item properties

  • ✅ When items must be processed separately for business logic

Decision Shortcut:

  • Need to look at multiple items? → Use "All Items" mode

  • Each item completely independent? → Use "Each Item" mode

  • Not sure? → Use "All Items" mode (you can always loop inside)

Why "All Items" is faster — the per-item boundary

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

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

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

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

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

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


Data Access Patterns

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

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

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

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


Critical: Webhook Data Structure

MOST COMMON MISTAKE: Webhook data is nested under .body

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

  1. Empty code / missing return — always end with return [...], and make sure every branch returns.

  2. 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.

  3. Return shape — prefer return [{json:{...}}]. A bare return {…} auto-wraps in All Items mode, but returning a primitive (string/number) or null is what actually fails.

  4. Missing null checks — use optional chaining: item.json?.user?.email || 'fallback'.

  5. Webhook body nesting — $json.email is undefined; use $json.body.email.

  6. Auth helpers blocked (httpRequestWithAuthentication) and $env blocked — 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 executions

Sandbox (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 .json before 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: 1 is 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 with paired_item_no_info.

  • Node reference syntax: $('Node').first().json or $('Node').all() — never .json directly 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 forExampleNot Code
Loops or recursion no node expressesflatten a tree of unknown depth; multi-pass matchingone level of nesting → Split Out
State across runscounters or cursors in $getWorkflowStaticData"only new items" → Remove Duplicates across executions
Parsing no node handlesfixed-width records, a custom log formatCSV / XLSX / PDF / XML / HTML → Extract From File, XML, HTML
An allowlisted libraryrequire('some-lib') the instance allowshashing / HMAC / UUID → Crypto
Heavy all-items workthousands of items, transform-bound, stated or measureda 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 dataa 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 nodes

  • Code 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; notes starts with Why 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 .body if 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

n8n Documentation

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

COMMON_PATTERNS.md

Common Patterns - JavaScript Code Node

Production-tested patterns for n8n Code nodes. These patterns are proven in real workflows.

Native first. Every pattern below opens with the native nodes that do the same job. Use the Code version only when the job passed the gate in SKILL.md (no native node fits, and it is on the closed list), and give the node a Why Code: note. The job-to-node table in using-n8n-mcp-skills lists the native nodes.


Overview

This guide covers the 10 most useful Code node patterns for n8n workflows. Each pattern includes:

  • Use Case: When to use this pattern

  • Key Techniques: Important coding techniques demonstrated

  • Complete Example: Working code you can adapt

  • Variations: Common modifications

Pattern Categories:

  • Data Aggregation (Patterns 1, 5, 10)

  • Content Processing (Patterns 2, 3)

  • Data Validation & Comparison (Patterns 4)

  • Data Transformation (Patterns 5, 6, 7)

  • Output Formatting (Pattern 8)

  • Filtering & Ranking (Pattern 9)


Pattern 1: Multi-Source Data Aggregation

Native first: Merge (append, or combineByFields on a shared key) joins sources; Edit Fields gives each source the same shape. Use this pattern when the sources' shapes differ too much for one Merge.

Use Case: Combining data from multiple APIs, RSS feeds, webhooks, or databases

When to use:

  • Collecting data from multiple services

  • Normalizing different API response formats

  • Merging data sources into unified structure

  • Building aggregated reports

Key Techniques: Loop iteration, conditional parsing, data normalization

Complete Example
// Process and structure data collected from multiple sources
const allItems = $input.all();
let processedArticles = [];

// Handle different source formats
for (const item of allItems) {
  const sourceName = item.json.name || 'Unknown';
  const sourceData = item.json;

  // Parse source-specific structure - Hacker News format
  if (sourceName === 'Hacker News' && sourceData.hits) {
    for (const hit of sourceData.hits) {
      processedArticles.push({
        title: hit.title,
        url: hit.url,
        summary: hit.story_text || 'No summary',
        source: 'Hacker News',
        score: hit.points || 0,
        fetchedAt: new Date().toISOString()
      });
    }
  }

  // Parse source-specific structure - Reddit format
  else if (sourceName === 'Reddit' && sourceData.data?.children) {
    for (const post of sourceData.data.children) {
      processedArticles.push({
        title: post.data.title,
        url: post.data.url,
        summary: post.data.selftext || 'No summary',
        source: 'Reddit',
        score: post.data.score || 0,
        fetchedAt: new Date().toISOString()
      });
    }
  }

  // Parse source-specific structure - RSS feed format
  else if (sourceName === 'RSS' && sourceData.items) {
    for (const rssItem of sourceData.items) {
      processedArticles.push({
        title: rssItem.title,
        url: rssItem.link,
        summary: rssItem.description || 'No summary',
        source: 'RSS Feed',
        score: 0,
        fetchedAt: new Date().toISOString()
      });
    }
  }
}

// Sort by score (highest first)
processedArticles.sort((a, b) => b.score - a.score);

return processedArticles.map(article => ({json: article}));
Variations
// Variation 1: Add source weighting
for (const article of processedArticles) {
  const weights = {
    'Hacker News': 1.5,
    'Reddit': 1.0,
    'RSS Feed': 0.8
  };

  article.weightedScore = article.score * (weights[article.source] || 1.0);
}

// Variation 2: Filter by minimum score
processedArticles = processedArticles.filter(article => article.score >= 10);

// Variation 3: Deduplicate by URL
const seen = new Set();
processedArticles = processedArticles.filter(article => {
  if (seen.has(article.url)) {
    return false;
  }
  seen.add(article.url);
  return true;
});

Pattern 2: Regex Filtering & Pattern Matching

Native first: a Filter or IF condition with the regex / contains string operator keeps or routes matching items. Use this pattern when you need to count or extract many matches per item.

Use Case: Content analysis, keyword extraction, mention tracking, text parsing

When to use:

  • Extracting mentions or tags from text

  • Finding patterns in unstructured data

  • Counting keyword occurrences

  • Validating formats (emails, phone numbers)

Key Techniques: Regex matching, object aggregation, sorting/ranking

Complete Example
// Extract and track mentions using regex patterns
const etfPattern = /\b([A-Z]{2,5})\b/g;
const knownETFs = ['VOO', 'VTI', 'VT', 'SCHD', 'QYLD', 'VXUS', 'SPY', 'QQQ'];

const etfMentions = {};

for (const item of $input.all()) {
  const data = item.json.data;

  // Skip if no data or children
  if (!data?.children) continue;

  for (const post of data.children) {
    // Combine title and body text
    const title = post.data.title || '';
    const body = post.data.selftext || '';
    const combinedText = (title + ' ' + body).toUpperCase();

    // Find all matches
    const matches = combinedText.match(etfPattern);

    if (matches) {
      for (const match of matches) {
        // Only count known ETFs
        if (knownETFs.includes(match)) {
          if (!etfMentions[match]) {
            etfMentions[match] = {
              count: 0,
              totalScore: 0,
              posts: []
            };
          }

          etfMentions[match].count++;
          etfMentions[match].totalScore += post.data.score || 0;
          etfMentions[match].posts.push({
            title: post.data.title,
            url: post.data.url,
            score: post.data.score
          });
        }
      }
    }
  }
}

// Convert to array and sort by mention count
return Object.entries(etfMentions)
  .map(([etf, data]) => ({
    json: {
      etf,
      mentions: data.count,
      totalScore: data.totalScore,
      averageScore: data.totalScore / data.count,
      topPosts: data.posts
        .sort((a, b) => b.score - a.score)
        .slice(0, 3)
    }
  }))
  .sort((a, b) => b.json.mentions - a.json.mentions);
Variations
// Variation 1: Email extraction
const emailPattern = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g;
const emails = text.match(emailPattern) || [];

// Variation 2: Phone number extraction
const phonePattern = /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g;
const phones = text.match(phonePattern) || [];

// Variation 3: Hashtag extraction
const hashtagPattern = /#(\w+)/g;
const hashtags = [];
let match;
while ((match = hashtagPattern.exec(text)) !== null) {
  hashtags.push(match[1]);
}

// Variation 4: URL extraction
const urlPattern = /https?:\/\/[^\s]+/g;
const urls = text.match(urlPattern) || [];

Pattern 3: Markdown Parsing & Structured Data Extraction

Native first: Markdown (to HTML) + HTML extractHtmlContent with CSS selectors, or Extract From File for CSV / XLSX / PDF. This pattern is for free-form text no node parses (a parsing exception).

Use Case: Parsing formatted text, extracting structured fields, content transformation

When to use:

  • Parsing markdown or HTML

  • Extracting data from structured text

  • Converting formatted content to JSON

  • Processing documentation or articles

Key Techniques: Regex grouping, helper functions, data normalization, while loops for iteration

Complete Example
// Parse markdown and extract structured information
const markdown = $input.first().json.data.markdown;
const adRegex = /##\s*(.*?)\n(.*?)(?=\n##|\n---|$)/gs;

const ads = [];
let match;

// Helper function to parse time strings to minutes
function parseTimeToMinutes(timeStr) {
  if (!timeStr) return 999999;  // Sort unparseable times last

  const hourMatch = timeStr.match(/(\d+)\s*hour/);
  const dayMatch = timeStr.match(/(\d+)\s*day/);
  const minMatch = timeStr.match(/(\d+)\s*min/);

  let totalMinutes = 0;
  if (dayMatch) totalMinutes += parseInt(dayMatch[1]) * 1440;  // 24 * 60
  if (hourMatch) totalMinutes += parseInt(hourMatch[1]) * 60;
  if (minMatch) totalMinutes += parseInt(minMatch[1]);

  return totalMinutes;
}

// Extract all job postings from markdown
while ((match = adRegex.exec(markdown)) !== null) {
  const title = match[1]?.trim() || 'No title';
  const content = match[2]?.trim() || '';

  // Extract structured fields from content
  const districtMatch = content.match(/\*\*District:\*\*\s*(.*?)(?:\n|$)/);
  const salaryMatch = content.match(/\*\*Salary:\*\*\s*(.*?)(?:\n|$)/);
  const timeMatch = content.match(/Posted:\s*(.*?)\*/);

  ads.push({
    title: title,
    district: districtMatch?.[1].trim() || 'Unknown',
    salary: salaryMatch?.[1].trim() || 'Not specified',
    postedTimeAgo: timeMatch?.[1] || 'Unknown',
    timeInMinutes: parseTimeToMinutes(timeMatch?.[1]),
    fullContent: content,
    extractedAt: new Date().toISOString()
  });
}

// Sort by recency (posted time)
ads.sort((a, b) => a.timeInMinutes - b.timeInMinutes);

return ads.map(ad => ({json: ad}));
Variations
// Variation 1: Parse HTML table to JSON
const tableRegex = /<tr>(.*?)<\/tr>/gs;
const cellRegex = /<td>(.*?)<\/td>/g;

const rows = [];
let tableMatch;

while ((tableMatch = tableRegex.exec(htmlTable)) !== null) {
  const cells = [];
  let cellMatch;

  while ((cellMatch = cellRegex.exec(tableMatch[1])) !== null) {
    cells.push(cellMatch[1].trim());
  }

  if (cells.length > 0) {
    rows.push(cells);
  }
}

// Variation 2: Extract code blocks from markdown
const codeBlockRegex = /```(\w+)?\n(.*?)```/gs;
const codeBlocks = [];

while ((match = codeBlockRegex.exec(markdown)) !== null) {
  codeBlocks.push({
    language: match[1] || 'plain',
    code: match[2].trim()
  });
}

// Variation 3: Parse YAML frontmatter
const frontmatterRegex = /^---\n(.*?)\n---/s;
const frontmatterMatch = content.match(frontmatterRegex);

if (frontmatterMatch) {
  const yamlLines = frontmatterMatch[1].split('\n');
  const metadata = {};

  for (const line of yamlLines) {
    const [key, ...valueParts] = line.split(':');
    if (key && valueParts.length > 0) {
      metadata[key.trim()] = valueParts.join(':').trim();
    }
  }
}

Pattern 4: JSON Comparison & Validation

Native first: Compare Datasets (outputs: in A only / same / different / in B only) diffs two lists on a key. Use this pattern for a deep diff inside one pair of objects.

Use Case: Workflow versioning, configuration validation, change detection, data integrity

When to use:

  • Comparing two versions of data

  • Detecting changes in configurations

  • Validating data consistency

  • Checking for differences

Key Techniques: JSON ordering, base64 decoding, deep comparison, object manipulation

Complete Example
// Compare and validate JSON objects from different sources
const orderJsonKeys = (jsonObj) => {
  const ordered = {};
  Object.keys(jsonObj).sort().forEach(key => {
    ordered[key] = jsonObj[key];
  });
  return ordered;
};

const allItems = $input.all();

// Assume first item is base64-encoded original, second is current
const origWorkflow = JSON.parse(
  Buffer.from(allItems[0].json.content, 'base64').toString()
);
const currentWorkflow = allItems[1].json;

// Order keys for consistent comparison
const orderedOriginal = orderJsonKeys(origWorkflow);
const orderedCurrent = orderJsonKeys(currentWorkflow);

// Deep comparison
const isSame = JSON.stringify(orderedOriginal) === JSON.stringify(orderedCurrent);

// Find differences
const differences = [];
for (const key of Object.keys(orderedOriginal)) {
  if (JSON.stringify(orderedOriginal[key]) !== JSON.stringify(orderedCurrent[key])) {
    differences.push({
      field: key,
      original: orderedOriginal[key],
      current: orderedCurrent[key]
    });
  }
}

// Check for new keys
for (const key of Object.keys(orderedCurrent)) {
  if (!(key in orderedOriginal)) {
    differences.push({
      field: key,
      original: null,
      current: orderedCurrent[key],
      status: 'new'
    });
  }
}

return [{
  json: {
    identical: isSame,
    differenceCount: differences.length,
    differences: differences,
    original: orderedOriginal,
    current: orderedCurrent,
    comparedAt: new Date().toISOString()
  }
}];
Variations
// Variation 1: Simple equality check
const isEqual = JSON.stringify(obj1) === JSON.stringify(obj2);

// Variation 2: Deep diff with detailed changes
function deepDiff(obj1, obj2, path = '') {
  const changes = [];

  for (const key in obj1) {
    const currentPath = path ? `${path}.${key}` : key;

    if (!(key in obj2)) {
      changes.push({type: 'removed', path: currentPath, value: obj1[key]});
    } else if (typeof obj1[key] === 'object' && typeof obj2[key] === 'object') {
      changes.push(...deepDiff(obj1[key], obj2[key], currentPath));
    } else if (obj1[key] !== obj2[key]) {
      changes.push({
        type: 'modified',
        path: currentPath,
        from: obj1[key],
        to: obj2[key]
      });
    }
  }

  for (const key in obj2) {
    if (!(key in obj1)) {
      const currentPath = path ? `${path}.${key}` : key;
      changes.push({type: 'added', path: currentPath, value: obj2[key]});
    }
  }

  return changes;
}

// Variation 3: Schema validation
function validateSchema(data, schema) {
  const errors = [];

  for (const field of schema.required || []) {
    if (!(field in data)) {
      errors.push(`Missing required field: ${field}`);
    }
  }

  for (const [field, type] of Object.entries(schema.types || {})) {
    if (field in data && typeof data[field] !== type) {
      errors.push(`Field ${field} should be ${type}, got ${typeof data[field]}`);
    }
  }

  return {
    valid: errors.length === 0,
    errors
  };
}

Pattern 5: CRM Data Transformation

Native first: Edit Fields with short expressions maps a form to a CRM shape, or the CRM node's own field slots do it directly. Use this pattern only when the mapping needs loops or several statements per field.

Use Case: Lead enrichment, data normalization, API preparation, form data processing

When to use:

  • Processing form submissions

  • Preparing data for CRM APIs

  • Normalizing contact information

  • Enriching lead data

Key Techniques: Object destructuring, data mapping, format conversion, field splitting

Complete Example
// Transform form data into CRM-compatible format
const item = $input.all()[0];
const {
  name,
  email,
  phone,
  company,
  course_interest,
  message,
  timestamp
} = item.json;

// Split name into first and last
const nameParts = name.split(' ');
const firstName = nameParts[0] || '';
const lastName = nameParts.slice(1).join(' ') || 'Unknown';

// Format phone number
const cleanPhone = phone.replace(/[^\d]/g, '');  // Remove non-digits

// Build CRM data structure
const crmData = {
  data: {
    type: 'Contact',
    attributes: {
      first_name: firstName,
      last_name: lastName,
      email1: email,
      phone_work: cleanPhone,
      account_name: company,
      description: `Course Interest: ${course_interest}\n\nMessage: ${message}\n\nSubmitted: ${timestamp}`,
      lead_source: 'Website Form',
      status: 'New'
    }
  },
  metadata: {
    original_submission: timestamp,
    processed_at: new Date().toISOString()
  }
};

return [{
  json: {
    ...item.json,
    crmData,
    processed: true
  }
}];
Variations
// Variation 1: Multiple contact processing
const contacts = $input.all();

return contacts.map(item => {
  const data = item.json;
  const [firstName, ...lastNameParts] = data.name.split(' ');

  return {
    json: {
      firstName,
      lastName: lastNameParts.join(' ') || 'Unknown',
      email: data.email.toLowerCase(),
      phone: data.phone.replace(/[^\d]/g, ''),
      tags: [data.source, data.interest_level].filter(Boolean)
    }
  };
});

// Variation 2: Field validation and normalization
function normalizePContact(raw) {
  return {
    first_name: raw.firstName?.trim() || '',
    last_name: raw.lastName?.trim() || 'Unknown',
    email: raw.email?.toLowerCase().trim() || '',
    phone: raw.phone?.replace(/[^\d]/g, '') || '',
    company: raw.company?.trim() || 'Unknown',
    title: raw.title?.trim() || '',
    valid: Boolean(raw.email && raw.firstName)
  };
}

// Variation 3: Lead scoring
function calculateLeadScore(data) {
  let score = 0;

  if (data.email) score += 10;
  if (data.phone) score += 10;
  if (data.company) score += 15;
  if (data.title?.toLowerCase().includes('director')) score += 20;
  if (data.title?.toLowerCase().includes('manager')) score += 15;
  if (data.message?.length > 100) score += 10;

  return score;
}

Pattern 6: Release Information Processing

Native first: the GitHub node fetches releases; Sort + Limit picks the latest; Edit Fields shapes them. Use this pattern for semver comparison or changelog parsing.

Use Case: Version management, changelog parsing, release notes generation, GitHub API processing

When to use:

  • Processing GitHub releases

  • Filtering stable versions

  • Generating changelog summaries

  • Extracting version information

Key Techniques: Array filtering, conditional field extraction, date formatting, string manipulation

Complete Example
// Extract and filter stable releases from GitHub API
const allReleases = $input.first().json;

const stableReleases = allReleases
  .filter(release => !release.prerelease && !release.draft)
  .slice(0, 10)
  .map(release => {
    // Extract highlights section from changelog
    const body = release.body || '';
    let highlights = 'No highlights available';

    if (body.includes('## Highlights:')) {
      highlights = body.split('## Highlights:')[1]?.split('##')[0]?.trim();
    } else {
      // Fallback to first 500 chars
      highlights = body.substring(0, 500) + '...';
    }

    return {
      tag: release.tag_name,
      name: release.name,
      published: release.published_at,
      publishedDate: new Date(release.published_at).toLocaleDateString(),
      author: release.author.login,
      url: release.html_url,
      changelog: body,
      highlights: highlights,
      assetCount: release.assets.length,
      assets: release.assets.map(asset => ({
        name: asset.name,
        size: asset.size,
        downloadCount: asset.download_count,
        downloadUrl: asset.browser_download_url
      }))
    };
  });

return stableReleases.map(release => ({json: release}));
Variations
// Variation 1: Version comparison
function compareVersions(v1, v2) {
  const parts1 = v1.replace('v', '').split('.').map(Number);
  const parts2 = v2.replace('v', '').split('.').map(Number);

  for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
    const num1 = parts1[i] || 0;
    const num2 = parts2[i] || 0;

    if (num1 > num2) return 1;
    if (num1 < num2) return -1;
  }

  return 0;
}

// Variation 2: Breaking change detection
function hasBreakingChanges(changelog) {
  const breakingKeywords = [
    'BREAKING CHANGE',
    'breaking change',
    'BC:',
    '💥'
  ];

  return breakingKeywords.some(keyword => changelog.includes(keyword));
}

// Variation 3: Extract version numbers
const versionPattern = /v?(\d+)\.(\d+)\.(\d+)/;
const match = tagName.match(versionPattern);

if (match) {
  const [_, major, minor, patch] = match;
  const version = {major: parseInt(major), minor: parseInt(minor), patch: parseInt(patch)};
}

Pattern 7: Array Transformation with Context

Native first: this is usually Edit Fields — computed fields are short expressions ({{ $json.price * $json.qty }}), with Split Out / Aggregate around it for arrays. Code only if the fields need multi-statement logic.

Use Case: Quick data transformation, field mapping, adding computed fields

When to use:

  • Transforming arrays with additional context

  • Adding calculated fields

  • Simplifying complex objects

  • Pluralization logic

Key Techniques: Array methods chaining, ternary operators, computed properties

Complete Example
// Transform releases with contextual information
const releases = $input.first().json
  .filter(release => !release.prerelease && !release.draft)
  .slice(0, 10)
  .map(release => ({
    version: release.tag_name,
    assetCount: release.assets.length,
    assetsCountText: release.assets.length === 1 ? 'file' : 'files',
    downloadUrl: release.html_url,
    isRecent: new Date(release.published_at) > new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
    age: Math.floor((Date.now() - new Date(release.published_at)) / (24 * 60 * 60 * 1000)),
    ageText: `${Math.floor((Date.now() - new Date(release.published_at)) / (24 * 60 * 60 * 1000))} days ago`
  }));

return releases.map(release => ({json: release}));
Variations
// Variation 1: Add ranking
const items = $input.all()
  .sort((a, b) => b.json.score - a.json.score)
  .map((item, index) => ({
    json: {
      ...item.json,
      rank: index + 1,
      medal: index < 3 ? ['🥇', '🥈', '🥉'][index] : ''
    }
  }));

// Variation 2: Add percentage calculations
const total = $input.all().reduce((sum, item) => sum + item.json.value, 0);

const itemsWithPercentage = $input.all().map(item => ({
  json: {
    ...item.json,
    percentage: ((item.json.value / total) * 100).toFixed(2) + '%'
  }
}));

// Variation 3: Add category labels
const categorize = (value) => {
  if (value > 100) return 'High';
  if (value > 50) return 'Medium';
  return 'Low';
};

const categorized = $input.all().map(item => ({
  json: {
    ...item.json,
    category: categorize(item.json.value)
  }
}));

Pattern 8: Slack Block Kit Formatting

Native first: the Slack node takes the blocks as an expression in its Blocks field (={{ { "blocks": [...] } }}, see NODE_FAMILY_GOTCHAS.md in n8n-node-configuration). Build blocks in Code only when their number depends on loops over data.

Use Case: Chat notifications, rich message formatting, interactive messages

When to use:

  • Sending formatted Slack messages

  • Creating interactive notifications

  • Building rich content for chat platforms

  • Status reports and alerts

Key Techniques: Template literals, nested objects, Block Kit syntax, date formatting

Complete Example
// Create Slack-formatted message with structured blocks
const date = new Date().toISOString().split('T')[0];
const data = $input.first().json;

return [{
  json: {
    text: `Daily Report - ${date}`,  // Fallback text
    blocks: [
      {
        type: "header",
        text: {
          type: "plain_text",
          text: `📊 Daily Security Report - ${date}`
        }
      },
      {
        type: "section",
        text: {
          type: "mrkdwn",
          text: `*Status:* ${data.status === 'ok' ? '✅ All Clear' : '⚠️ Issues Detected'}\n*Alerts:* ${data.alertCount || 0}\n*Updated:* ${new Date().toLocaleString()}`
        }
      },
      {
        type: "divider"
      },
      {
        type: "section",
        fields: [
          {
            type: "mrkdwn",
            text: `*Failed Logins:*\n${data.failedLogins || 0}`
          },
          {
            type: "mrkdwn",
            text: `*API Errors:*\n${data.apiErrors || 0}`
          },
          {
            type: "mrkdwn",
            text: `*Uptime:*\n${data.uptime || '100%'}`
          },
          {
            type: "mrkdwn",
            text: `*Response Time:*\n${data.avgResponseTime || 'N/A'}ms`
          }
        ]
      },
      {
        type: "context",
        elements: [{
          type: "mrkdwn",
          text: `Report generated automatically by n8n workflow`
        }]
      }
    ]
  }
}];
Variations
// Variation 1: Interactive buttons
const blocksWithButtons = [
  {
    type: "section",
    text: {
      type: "mrkdwn",
      text: "Would you like to approve this request?"
    },
    accessory: {
      type: "button",
      text: {
        type: "plain_text",
        text: "Approve"
      },
      style: "primary",
      value: "approve",
      action_id: "approve_button"
    }
  }
];

// Variation 2: List formatting
const items = ['Item 1', 'Item 2', 'Item 3'];
const formattedList = items.map((item, i) => `${i + 1}. ${item}`).join('\n');

// Variation 3: Status indicators
function getStatusEmoji(status) {
  const statusMap = {
    'success': '✅',
    'warning': '⚠️',
    'error': '❌',
    'info': 'ℹ️'
  };

  return statusMap[status] || '•';
}

// Variation 4: Truncate long messages
function truncate(text, maxLength = 3000) {
  if (text.length <= maxLength) return text;
  return text.substring(0, maxLength - 3) + '...';
}

Pattern 9: Top N Filtering & Ranking

Native first: Filter (threshold) → Sort (by score, descending) → Limit (N). Use this pattern when the score itself needs a multi-step calculation.

Use Case: RAG pipelines, ranking algorithms, result filtering, leaderboards

When to use:

  • Getting top results by score

  • Filtering best/worst performers

  • Building leaderboards

  • Relevance ranking

Key Techniques: Sorting, slicing, null coalescing, score calculations

Complete Example
// Filter and rank by similarity score, return top results
const ragResponse = $input.item.json;
const chunks = ragResponse.chunks || [];

// Sort by similarity (highest first)
const topChunks = chunks
  .sort((a, b) => (b.similarity || 0) - (a.similarity || 0))
  .slice(0, 6);

return [{
  json: {
    query: ragResponse.query,
    topChunks: topChunks,
    count: topChunks.length,
    maxSimilarity: topChunks[0]?.similarity || 0,
    minSimilarity: topChunks[topChunks.length - 1]?.similarity || 0,
    averageSimilarity: topChunks.reduce((sum, chunk) => sum + (chunk.similarity || 0), 0) / topChunks.length
  }
}];
Variations
// Variation 1: Top N with minimum threshold
const threshold = 0.7;
const topItems = $input.all()
  .filter(item => item.json.score >= threshold)
  .sort((a, b) => b.json.score - a.json.score)
  .slice(0, 10);

// Variation 2: Bottom N (worst performers)
const bottomItems = $input.all()
  .sort((a, b) => a.json.score - b.json.score)  // Ascending
  .slice(0, 5);

// Variation 3: Top N by multiple criteria
const ranked = $input.all()
  .map(item => ({
    ...item,
    compositeScore: (item.json.relevance * 0.6) + (item.json.recency * 0.4)
  }))
  .sort((a, b) => b.compositeScore - a.compositeScore)
  .slice(0, 10);

// Variation 4: Percentile filtering
const allScores = $input.all().map(item => item.json.score).sort((a, b) => b - a);
const percentile95 = allScores[Math.floor(allScores.length * 0.05)];

const topPercentile = $input.all().filter(item => item.json.score >= percentile95);

Pattern 10: String Aggregation & Reporting

Native first: Summarize with concatenate (per group) or Aggregate, then one expression with .join('\n') in the message field. Use this pattern for multi-section reports with loops.

Use Case: Report generation, log aggregation, content concatenation, summary creation

When to use:

  • Combining multiple text outputs

  • Generating reports from data

  • Aggregating logs or messages

  • Creating formatted summaries

Key Techniques: Array joining, string concatenation, template literals, timestamp handling

Complete Example
// Aggregate multiple text inputs into formatted report
const allItems = $input.all();

// Collect all messages
const messages = allItems.map(item => item.json.message);

// Build report
const header = `🎯 **Daily Summary Report**\n📅 ${new Date().toLocaleString()}\n📊 Total Items: ${messages.length}\n\n`;
const divider = '\n\n---\n\n';
const footer = `\n\n---\n\n✅ Report generated at ${new Date().toISOString()}`;

const finalReport = header + messages.join(divider) + footer;

return [{
  json: {
    report: finalReport,
    messageCount: messages.length,
    generatedAt: new Date().toISOString(),
    reportLength: finalReport.length
  }
}];
Variations
// Variation 1: Numbered list
const numberedReport = allItems
  .map((item, index) => `${index + 1}. ${item.json.title}\n   ${item.json.description}`)
  .join('\n\n');

// Variation 2: Markdown table
const headers = '| Name | Status | Score |\n|------|--------|-------|\n';
const rows = allItems
  .map(item => `| ${item.json.name} | ${item.json.status} | ${item.json.score} |`)
  .join('\n');

const table = headers + rows;

// Variation 3: HTML report
const htmlReport = `
<!DOCTYPE html>
<html>
<head><title>Report</title></head>
<body>
  <h1>Report - ${new Date().toLocaleDateString()}</h1>
  <ul>
    ${allItems.map(item => `<li>${item.json.title}: ${item.json.value}</li>`).join('\n    ')}
  </ul>
</body>
</html>
`;

// Variation 4: JSON summary
const summary = {
  generated: new Date().toISOString(),
  totalItems: allItems.length,
  items: allItems.map(item => item.json),
  statistics: {
    total: allItems.reduce((sum, item) => sum + (item.json.value || 0), 0),
    average: allItems.reduce((sum, item) => sum + (item.json.value || 0), 0) / allItems.length,
    max: Math.max(...allItems.map(item => item.json.value || 0)),
    min: Math.min(...allItems.map(item => item.json.value || 0))
  }
};

Choosing the Right Pattern

Pattern Selection Guide
Your GoalUse Pattern
Combine multiple API responsesPattern 1 (Multi-source Aggregation)
Extract mentions or keywordsPattern 2 (Regex Filtering)
Parse formatted textPattern 3 (Markdown Parsing)
Detect changes in dataPattern 4 (JSON Comparison)
Prepare form data for CRMPattern 5 (CRM Transformation)
Process GitHub releasesPattern 6 (Release Processing)
Add computed fieldsPattern 7 (Array Transformation)
Format Slack messagesPattern 8 (Block Kit Formatting)
Get top resultsPattern 9 (Top N Filtering)
Create text reportsPattern 10 (String Aggregation)
Combining Patterns

Many real workflows combine multiple patterns:

// Example: Multi-source aggregation + Top N filtering
const allItems = $input.all();
const aggregated = [];

// Pattern 1: Aggregate from different sources
for (const item of allItems) {
  // ... aggregation logic
  aggregated.push(normalizedItem);
}

// Pattern 9: Get top 10 by score
const top10 = aggregated
  .sort((a, b) => b.score - a.score)
  .slice(0, 10);

// Pattern 10: Generate report
const report = `Top 10 Items:\n\n${top10.map((item, i) => `${i + 1}. ${item.title} (${item.score})`).join('\n')}`;

return [{json: {report, items: top10}}];

Summary

Most Useful Patterns:

  1. Multi-source Aggregation - Combining data from APIs, databases

  2. Top N Filtering - Rankings, leaderboards, best results

  3. Data Transformation - CRM data, field mapping, enrichment

Key Techniques Across Patterns:

  • Array methods (map, filter, reduce, sort, slice)

  • Regex for pattern matching

  • Object manipulation and destructuring

  • Error handling with optional chaining

  • Template literals for formatting

See Also:


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 this.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;

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