[ PROMPT_NODE_28081 ]
N8n Code Javascript – COMMON PATTERNS
[ SKILL_DOCUMENTATION ]
# Common Patterns - JavaScript Code Node
Production-tested patterns for n8n Code nodes. These patterns are proven in real workflows.
---
## 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
**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
```javascript
// 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
```javascript
// 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
**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
```javascript
// 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
```javascript
// 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 = /bd{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
**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
```javascript
// 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
```javascript
// Variation 1: Parse HTML table to JSON
const tableRegex = /(.*?) /gs;
const cellRegex = /(.*?) /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
**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
```javascript
// 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
```javascript
// 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
**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
```javascript
// 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}nnMessage: ${message}nnSubmitted: ${timestamp}`,
lead_source: 'Website Form',
status: 'New'
}
},
metadata: {
original_submission: timestamp,
processed_at: new Date().toISOString()
}
};
return [{
json: {
...item.json,
crmData,
processed: true
}
}];
```
### Variations
```javascript
// 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
**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
```javascript
// 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
```javascript
// 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
Source: claude-code-templates (MIT). See About Us for full credits.