n8n-code-nodes

SKILL.md

n8n Code Nodes

Write production-quality JavaScript and Python code inside n8n Code nodes. JavaScript is recommended for 95% of use cases; Python is available for specific needs.

When to Use Other Skills

  • Expression syntax ({{ }}) in non-code node fields → use n8n-node-expert
  • Workflow design, patterns, MCP tools → use n8n-workflow-architect
  • Writing code inside Code nodes → you're in the right place

When to Use Code Node vs Other Nodes

Use Code node for:

  • Complex transformations requiring multiple steps
  • Custom calculations or business logic
  • Data aggregation across items
  • Recursive operations
  • API response parsing with complex structure
  • Multi-step conditionals

Use simpler nodes instead:

  • Simple field mapping → Set node
  • Basic filtering → Filter node
  • Simple if/else → IF or Switch node
  • HTTP requests only → HTTP Request node

JavaScript (Primary Language)

Quick Start Template

const items = $input.all();

const processed = items.map(item => ({
  json: {
    ...item.json,
    processed: true,
    timestamp: new Date().toISOString()
  }
}));

return processed;

Three Critical Rules

1. Return format: always [{json: {...}}]

return [{json: {result: "success"}}];
return items.map(item => ({json: item.json}));
return [];  // empty result

return {json: {result: "success"}};     // missing array wrapper
return [{result: "success"}];            // missing json key
return "processed";                      // plain string

2. Webhook data is under .body

const email = $json.email;
const email = $json.body.email;

3. No expression syntax {{ }} — use JavaScript directly

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

Mode Selection

Run Once for All Items (default, recommended for 95% of cases):

  • Code executes once, receives all items
  • Access: $input.all(), $input.first()
  • Use for: aggregation, filtering, batch processing, comparing items
const allItems = $input.all();
const total = allItems.reduce((sum, item) => sum + (item.json.amount || 0), 0);
return [{json: {total, count: allItems.length, average: total / allItems.length}}];

Run Once for Each Item (specialized):

  • Code executes separately per item
  • Access: $input.item
  • Use for: per-item API calls, independent validation
const item = $input.item;
return [{json: {...item.json, processed: true}}];

Data Access Patterns

$input.all() — Get all items (most common):

const items = $input.all();
const filtered = items.filter(item => item.json.status === 'active');
return filtered.map(item => ({json: {id: item.json.id, name: item.json.name}}));

$input.first() — Get first item:

const data = $input.first().json;
return [{json: {result: processData(data)}}];

$node — Reference other nodes:

const webhookData = $node["Webhook"].json;
const apiData = $node["HTTP Request"].json;
return [{json: {combined: {webhook: webhookData, api: apiData}}}];

Built-in Functions

$helpers.httpRequest() — HTTP calls from Code:

const response = await $helpers.httpRequest({
  method: 'POST',
  url: 'https://api.example.com/data',
  headers: {'Authorization': 'Bearer token', 'Content-Type': 'application/json'},
  body: {query: $json.body.search_term}
});
return [{json: {data: response}}];

DateTime (Luxon) — Date operations:

const now = DateTime.now();
const formatted = now.toFormat('yyyy-MM-dd');
const nextWeek = now.plus({days: 7}).toFormat('yyyy-MM-dd');
const parsed = DateTime.fromISO($json.created_at).toFormat('MMMM dd, yyyy');
return [{json: {today: formatted, nextWeek, parsed}}];

$jmespath() — JSON queries:

const data = $input.first().json;
const adults = $jmespath(data, 'users[?age >= `18`]');
const names = $jmespath(data, 'users[*].name');
return [{json: {adults, names}}];

Production Patterns

1. Data Transformation & Enrichment

const items = $input.all();
return items.map(item => {
  const data = item.json;
  const nameParts = (data.name || '').split(' ');
  return {
    json: {
      first_name: nameParts[0] || '',
      last_name: nameParts.slice(1).join(' ') || '',
      email: data.email?.toLowerCase(),
      created_at: new Date().toISOString()
    }
  };
});

2. Multi-Source Aggregation

const items = $input.all();
const bySource = {};

for (const item of items) {
  const source = item.json.source || 'unknown';
  if (!bySource[source]) bySource[source] = {count: 0, total: 0};
  bySource[source].count++;
  bySource[source].total += item.json.amount || 0;
}

return [{json: {summary: bySource, timestamp: new Date().toISOString()}}];

3. Regex Extraction

const text = $input.first().json.body.message;
const emailPattern = /[\w.-]+@[\w.-]+\.\w+/g;
const emails = text.match(emailPattern) || [];
const urlPattern = /https?:\/\/[^\s]+/g;
const urls = text.match(urlPattern) || [];

return [{json: {emails, urls, hasAttachments: emails.length > 0 || urls.length > 0}}];

4. Top-N Filtering & Ranking

const items = $input.all();
const topItems = items
  .sort((a, b) => (b.json.score || 0) - (a.json.score || 0))
  .slice(0, 10);
return topItems.map(item => ({json: item.json}));

5. Threat Indicator Scoring

Pattern for security automation — score multiple indicators and produce confidence level:

const alert = $input.first().json;
let score = 0;
const reasons = [];

// Domain reputation
if (alert.sender_domain && KNOWN_BAD_DOMAINS.includes(alert.sender_domain)) {
  score += 0.4;
  reasons.push('known malicious domain');
}

// Attachment analysis
if (alert.has_attachment && /\.(exe|scr|bat|cmd|ps1)$/i.test(alert.attachment_name)) {
  score += 0.3;
  reasons.push('dangerous attachment type');
}

// SPF/DKIM
if (alert.spf_result === 'fail') { score += 0.15; reasons.push('SPF failure'); }
if (alert.dkim_result === 'fail') { score += 0.15; reasons.push('DKIM failure'); }

const confidence = score > 0.8 ? 'high' : score > 0.4 ? 'medium' : 'low';
return [{json: {score, confidence, reasons, alert_id: alert.id}}];

6. PDF/Report Data Parsing

Pattern for extracting structured data from API responses or parsed documents:

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

// Extract metrics from parsed report sections
const metrics = {
  emails_blocked: parseInt(rawData.summary?.match(/(\d+)\s+emails?\s+blocked/i)?.[1] || '0'),
  phishing_detected: parseInt(rawData.summary?.match(/(\d+)\s+phishing/i)?.[1] || '0'),
  users_trained: parseInt(rawData.training?.match(/(\d+)\s+completed/i)?.[1] || '0'),
  compliance_score: parseFloat(rawData.compliance?.match(/([\d.]+)%/)?.[1] || '0')
};

return [{json: {metrics, report_date: DateTime.now().toFormat('yyyy-MM'), raw_sections: Object.keys(rawData)}}];

7. Quebec Tax Calculation

const expense = $input.first().json;
const amount = parseFloat(expense.amount);
const TPS_RATE = 0.05;     // Federal GST
const TVQ_RATE = 0.09975;  // Quebec QST

let taxBreakdown;
if (expense.province === 'QC' && expense.category === 'restaurant') {
  // Quebec restaurant: separate TPS and TVQ
  const subtotal = amount / (1 + TPS_RATE + TVQ_RATE);
  taxBreakdown = {
    subtotal: subtotal.toFixed(2),
    tps: (subtotal * TPS_RATE).toFixed(2),
    tvq: (subtotal * TVQ_RATE).toFixed(2),
    total: amount.toFixed(2),
    tax_type: 'QC_TPS_TVQ'
  };
} else if (expense.is_foreign_saas) {
  taxBreakdown = {
    subtotal: amount.toFixed(2),
    tps: '0.00',
    tvq: '0.00',
    total: amount.toFixed(2),
    tax_type: 'FOREIGN_NO_ITC',
    note: 'Foreign SaaS - no input tax credit'
  };
} else {
  // Standard GST/HST
  const rate = expense.province === 'QC' ? TPS_RATE + TVQ_RATE : 0.13; // HST for ON etc.
  const subtotal = amount / (1 + rate);
  taxBreakdown = {
    subtotal: subtotal.toFixed(2),
    tax: (amount - subtotal).toFixed(2),
    total: amount.toFixed(2),
    tax_type: expense.province === 'QC' ? 'QC_COMBINED' : 'HST'
  };
}

return [{json: {...expense, ...taxBreakdown}}];

Error Prevention

Always validate input:

const items = $input.all();
if (!items || items.length === 0) return [];
if (!items[0].json) return [{json: {error: 'Invalid input format'}}];

Use try-catch for external calls:

try {
  const response = await $helpers.httpRequest({url: 'https://api.example.com/data'});
  return [{json: {success: true, data: response}}];
} catch (error) {
  return [{json: {success: false, error: error.message}}];
}

Use optional chaining:

const email = item.json.user.email;          // crashes if user is undefined
const email = item.json?.user?.email || '';   // safe

Debug with console.log:

console.log(`Processing ${items.length} items`);
console.log('First item:', JSON.stringify(items[0].json, null, 2));

For complete pattern library, see references/js-patterns.md For complete error guide, see references/js-errors.md For built-in function reference, see references/js-builtins.md


Python (Secondary — Use When Needed)

Python in n8n Code nodes is in beta. Use JavaScript unless you specifically need Python standard library features or are significantly more comfortable with Python.

Key Differences from JavaScript

Feature JavaScript Python
Data access $input.all() _input.all()
Current item $input.item _input.item
Node reference $node["Name"].json _node["Name"].json
HTTP requests $helpers.httpRequest() Not available — use urllib
DateTime DateTime (Luxon) datetime (standard library)
Return format [{json: {...}}] [{"json": {...}}]

Quick Start

items = _input.all()

processed = []
for item in items:
    processed.append({
        "json": {
            **item["json"],
            "processed": True,
            "timestamp": datetime.now().isoformat()
        }
    })

return processed

Python Limitations in n8n

  • No external packages (no pip install) — standard library only
  • No $helpers.httpRequest() — use urllib.request instead
  • No Luxon DateTime — use Python's datetime
  • Less community support and documentation
  • Performance may vary

When Python Makes Sense

  • Complex text processing (Python's regex and string handling)
  • Statistical calculations (using statistics module)
  • Data parsing with json, csv, xml standard modules
  • When you have existing Python logic to port

For complete Python guide, see references/python-guide.md


Deployment Checklist

  • Code is not empty, has meaningful logic
  • Return statement exists on all code paths
  • Return format: [{json: {...}}]
  • No {{ }} expression syntax (use JS/Python directly)
  • Webhook data accessed via .body
  • Null/undefined guards (optional chaining or explicit checks)
  • Error handling (try-catch for external calls)
  • Mode selected ("All Items" for most cases)
  • Tested with real data

Reference Files

Weekly Installs
3
First Seen
7 days ago
Installed on
opencode3
gemini-cli3
github-copilot3
codex3
kimi-cli3
amp3