783 lines
16 KiB
Markdown
783 lines
16 KiB
Markdown
# Data Access Patterns - JavaScript Code Node
|
|
|
|
Comprehensive guide to accessing data in n8n Code nodes using JavaScript.
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
In n8n Code nodes, you access data from previous nodes using built-in variables and methods. Understanding which method to use is critical for correct workflow execution.
|
|
|
|
**Data Access Priority** (by common usage):
|
|
1. **`$input.all()`** - Most common - Batch operations, aggregations
|
|
2. **`$input.first()`** - Very common - Single item operations
|
|
3. **`$input.item`** - Common - Each Item mode only
|
|
4. **`$node["NodeName"].json`** - Specific node references
|
|
5. **`$json`** - Direct current item (legacy, use `$input` instead)
|
|
|
|
---
|
|
|
|
## Pattern 1: $input.all() - Process All Items
|
|
|
|
**Usage**: Most common pattern for batch processing
|
|
|
|
**When to use:**
|
|
- Processing multiple records
|
|
- Aggregating data (sum, count, average)
|
|
- Filtering arrays
|
|
- Transforming datasets
|
|
- Comparing items
|
|
- Sorting or ranking
|
|
|
|
### Basic Usage
|
|
|
|
```javascript
|
|
// Get all items from previous node
|
|
const allItems = $input.all();
|
|
|
|
// allItems is an array of objects like:
|
|
// [
|
|
// {json: {id: 1, name: "Alice"}},
|
|
// {json: {id: 2, name: "Bob"}}
|
|
// ]
|
|
|
|
console.log(`Received ${allItems.length} items`);
|
|
|
|
return allItems;
|
|
```
|
|
|
|
### Example 1: Filter Active Items
|
|
|
|
```javascript
|
|
const allItems = $input.all();
|
|
|
|
// Filter only active items
|
|
const activeItems = allItems.filter(item => item.json.status === 'active');
|
|
|
|
return activeItems;
|
|
```
|
|
|
|
### Example 2: Transform All Items
|
|
|
|
```javascript
|
|
const allItems = $input.all();
|
|
|
|
// Map to new structure
|
|
const transformed = allItems.map(item => ({
|
|
json: {
|
|
id: item.json.id,
|
|
fullName: `${item.json.firstName} ${item.json.lastName}`,
|
|
email: item.json.email,
|
|
processedAt: new Date().toISOString()
|
|
}
|
|
}));
|
|
|
|
return transformed;
|
|
```
|
|
|
|
### Example 3: Aggregate Data
|
|
|
|
```javascript
|
|
const allItems = $input.all();
|
|
|
|
// Calculate total
|
|
const total = allItems.reduce((sum, item) => {
|
|
return sum + (item.json.amount || 0);
|
|
}, 0);
|
|
|
|
return [{
|
|
json: {
|
|
total,
|
|
count: allItems.length,
|
|
average: total / allItems.length
|
|
}
|
|
}];
|
|
```
|
|
|
|
### Example 4: Sort and Limit
|
|
|
|
```javascript
|
|
const allItems = $input.all();
|
|
|
|
// Get top 5 by score
|
|
const topFive = allItems
|
|
.sort((a, b) => (b.json.score || 0) - (a.json.score || 0))
|
|
.slice(0, 5);
|
|
|
|
return topFive.map(item => ({json: item.json}));
|
|
```
|
|
|
|
### Example 5: Group By Category
|
|
|
|
```javascript
|
|
const allItems = $input.all();
|
|
|
|
// Group items by category
|
|
const grouped = {};
|
|
|
|
for (const item of allItems) {
|
|
const category = item.json.category || 'Uncategorized';
|
|
|
|
if (!grouped[category]) {
|
|
grouped[category] = [];
|
|
}
|
|
|
|
grouped[category].push(item.json);
|
|
}
|
|
|
|
// Convert to array format
|
|
return Object.entries(grouped).map(([category, items]) => ({
|
|
json: {
|
|
category,
|
|
items,
|
|
count: items.length
|
|
}
|
|
}));
|
|
```
|
|
|
|
### Example 6: Deduplicate by ID
|
|
|
|
```javascript
|
|
const allItems = $input.all();
|
|
|
|
// Remove duplicates by ID
|
|
const seen = new Set();
|
|
const unique = [];
|
|
|
|
for (const item of allItems) {
|
|
const id = item.json.id;
|
|
|
|
if (!seen.has(id)) {
|
|
seen.add(id);
|
|
unique.push(item);
|
|
}
|
|
}
|
|
|
|
return unique;
|
|
```
|
|
|
|
---
|
|
|
|
## Pattern 2: $input.first() - Get First Item
|
|
|
|
**Usage**: Very common for single-item operations
|
|
|
|
**When to use:**
|
|
- Previous node returns single object
|
|
- Working with API responses
|
|
- Getting initial/first data point
|
|
- Configuration or metadata access
|
|
|
|
### Basic Usage
|
|
|
|
```javascript
|
|
// Get first item from previous node
|
|
const firstItem = $input.first();
|
|
|
|
// Access the JSON data
|
|
const data = firstItem.json;
|
|
|
|
console.log('First item:', data);
|
|
|
|
return [{json: data}];
|
|
```
|
|
|
|
### Example 1: Process Single API Response
|
|
|
|
```javascript
|
|
// Get API response (typically single object)
|
|
const response = $input.first().json;
|
|
|
|
// Extract what you need
|
|
return [{
|
|
json: {
|
|
userId: response.data.user.id,
|
|
userName: response.data.user.name,
|
|
status: response.status,
|
|
fetchedAt: new Date().toISOString()
|
|
}
|
|
}];
|
|
```
|
|
|
|
### Example 2: Transform Single Object
|
|
|
|
```javascript
|
|
const data = $input.first().json;
|
|
|
|
// Transform structure
|
|
return [{
|
|
json: {
|
|
id: data.id,
|
|
contact: {
|
|
email: data.email,
|
|
phone: data.phone
|
|
},
|
|
address: {
|
|
street: data.street,
|
|
city: data.city,
|
|
zip: data.zip
|
|
}
|
|
}
|
|
}];
|
|
```
|
|
|
|
### Example 3: Validate Single Item
|
|
|
|
```javascript
|
|
const item = $input.first().json;
|
|
|
|
// Validation logic
|
|
const isValid = item.email && item.email.includes('@');
|
|
|
|
return [{
|
|
json: {
|
|
...item,
|
|
valid: isValid,
|
|
validatedAt: new Date().toISOString()
|
|
}
|
|
}];
|
|
```
|
|
|
|
### Example 4: Extract Nested Data
|
|
|
|
```javascript
|
|
const response = $input.first().json;
|
|
|
|
// Navigate nested structure
|
|
const users = response.data?.users || [];
|
|
|
|
return users.map(user => ({
|
|
json: {
|
|
id: user.id,
|
|
name: user.profile?.name || 'Unknown',
|
|
email: user.contact?.email || 'no-email'
|
|
}
|
|
}));
|
|
```
|
|
|
|
### Example 5: Combine with Other Methods
|
|
|
|
```javascript
|
|
// Get first item's data
|
|
const firstData = $input.first().json;
|
|
|
|
// Use it to filter all items
|
|
const allItems = $input.all();
|
|
const matching = allItems.filter(item =>
|
|
item.json.category === firstData.targetCategory
|
|
);
|
|
|
|
return matching;
|
|
```
|
|
|
|
---
|
|
|
|
## Pattern 3: $input.item - Current Item (Each Item Mode)
|
|
|
|
**Usage**: Common in "Run Once for Each Item" mode
|
|
|
|
**When to use:**
|
|
- Mode is set to "Run Once for Each Item"
|
|
- Need to process items independently
|
|
- Per-item API calls or validations
|
|
- Item-specific error handling
|
|
|
|
**IMPORTANT**: Only use in "Each Item" mode. Will be undefined in "All Items" mode.
|
|
|
|
### Basic Usage
|
|
|
|
```javascript
|
|
// In "Run Once for Each Item" mode
|
|
const currentItem = $input.item;
|
|
const data = currentItem.json;
|
|
|
|
console.log('Processing item:', data.id);
|
|
|
|
return [{
|
|
json: {
|
|
...data,
|
|
processed: true
|
|
}
|
|
}];
|
|
```
|
|
|
|
### Example 1: Add Processing Metadata
|
|
|
|
```javascript
|
|
const item = $input.item;
|
|
|
|
return [{
|
|
json: {
|
|
...item.json,
|
|
processed: true,
|
|
processedAt: new Date().toISOString(),
|
|
processingDuration: Math.random() * 1000 // Simulated duration
|
|
}
|
|
}];
|
|
```
|
|
|
|
### Example 2: Per-Item Validation
|
|
|
|
```javascript
|
|
const item = $input.item;
|
|
const data = item.json;
|
|
|
|
// Validate this specific item
|
|
const errors = [];
|
|
|
|
if (!data.email) errors.push('Email required');
|
|
if (!data.name) errors.push('Name required');
|
|
if (data.age && data.age < 18) errors.push('Must be 18+');
|
|
|
|
return [{
|
|
json: {
|
|
...data,
|
|
valid: errors.length === 0,
|
|
errors: errors.length > 0 ? errors : undefined
|
|
}
|
|
}];
|
|
```
|
|
|
|
### Example 3: Item-Specific API Call
|
|
|
|
```javascript
|
|
const item = $input.item;
|
|
const userId = item.json.userId;
|
|
|
|
// Make API call specific to this item
|
|
const response = await $helpers.httpRequest({
|
|
method: 'GET',
|
|
url: `https://api.example.com/users/${userId}/details`
|
|
});
|
|
|
|
return [{
|
|
json: {
|
|
...item.json,
|
|
details: response
|
|
}
|
|
}];
|
|
```
|
|
|
|
### Example 4: Conditional Processing
|
|
|
|
```javascript
|
|
const item = $input.item;
|
|
const data = item.json;
|
|
|
|
// Process based on item type
|
|
if (data.type === 'premium') {
|
|
return [{
|
|
json: {
|
|
...data,
|
|
discount: 0.20,
|
|
tier: 'premium'
|
|
}
|
|
}];
|
|
} else {
|
|
return [{
|
|
json: {
|
|
...data,
|
|
discount: 0.05,
|
|
tier: 'standard'
|
|
}
|
|
}];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Pattern 4: $node - Reference Other Nodes
|
|
|
|
**Usage**: Less common, but powerful for specific scenarios
|
|
|
|
**When to use:**
|
|
- Need data from specific named node
|
|
- Combining data from multiple nodes
|
|
- Accessing metadata about workflow execution
|
|
|
|
### Basic Usage
|
|
|
|
```javascript
|
|
// Get output from specific node
|
|
const webhookData = $node["Webhook"].json;
|
|
const apiData = $node["HTTP Request"].json;
|
|
|
|
return [{
|
|
json: {
|
|
fromWebhook: webhookData,
|
|
fromAPI: apiData
|
|
}
|
|
}];
|
|
```
|
|
|
|
### Example 1: Combine Multiple Sources
|
|
|
|
```javascript
|
|
// Reference multiple nodes
|
|
const webhook = $node["Webhook"].json;
|
|
const database = $node["Postgres"].json;
|
|
const api = $node["HTTP Request"].json;
|
|
|
|
return [{
|
|
json: {
|
|
combined: {
|
|
webhook: webhook.body,
|
|
dbRecords: database.length,
|
|
apiResponse: api.status
|
|
},
|
|
processedAt: new Date().toISOString()
|
|
}
|
|
}];
|
|
```
|
|
|
|
### Example 2: Compare Across Nodes
|
|
|
|
```javascript
|
|
const oldData = $node["Get Old Data"].json;
|
|
const newData = $node["Get New Data"].json;
|
|
|
|
// Compare
|
|
const changes = {
|
|
added: newData.filter(n => !oldData.find(o => o.id === n.id)),
|
|
removed: oldData.filter(o => !newData.find(n => n.id === o.id)),
|
|
modified: newData.filter(n => {
|
|
const old = oldData.find(o => o.id === n.id);
|
|
return old && JSON.stringify(old) !== JSON.stringify(n);
|
|
})
|
|
};
|
|
|
|
return [{
|
|
json: {
|
|
changes,
|
|
summary: {
|
|
added: changes.added.length,
|
|
removed: changes.removed.length,
|
|
modified: changes.modified.length
|
|
}
|
|
}
|
|
}];
|
|
```
|
|
|
|
### Example 3: Access Node Metadata
|
|
|
|
```javascript
|
|
// Get data from specific execution path
|
|
const ifTrueBranch = $node["IF True"].json;
|
|
const ifFalseBranch = $node["IF False"].json;
|
|
|
|
// Use whichever branch executed
|
|
const result = ifTrueBranch || ifFalseBranch || {};
|
|
|
|
return [{json: result}];
|
|
```
|
|
|
|
---
|
|
|
|
## Critical: Webhook Data Structure
|
|
|
|
**MOST COMMON MISTAKE**: Forgetting webhook data is nested under `.body`
|
|
|
|
### The Problem
|
|
|
|
Webhook node wraps all incoming data under a `body` property. This catches many developers by surprise.
|
|
|
|
### Structure
|
|
|
|
```javascript
|
|
// Webhook node output structure:
|
|
{
|
|
"headers": {
|
|
"content-type": "application/json",
|
|
"user-agent": "...",
|
|
// ... other headers
|
|
},
|
|
"params": {},
|
|
"query": {},
|
|
"body": {
|
|
// ← YOUR DATA IS HERE
|
|
"name": "Alice",
|
|
"email": "alice@example.com",
|
|
"message": "Hello!"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Wrong vs Right
|
|
|
|
```javascript
|
|
// ❌ WRONG: Trying to access directly
|
|
const name = $json.name; // undefined
|
|
const email = $json.email; // undefined
|
|
|
|
// ✅ CORRECT: Access via .body
|
|
const name = $json.body.name; // "Alice"
|
|
const email = $json.body.email; // "alice@example.com"
|
|
|
|
// ✅ CORRECT: Extract body first
|
|
const webhookData = $json.body;
|
|
const name = webhookData.name; // "Alice"
|
|
const email = webhookData.email; // "alice@example.com"
|
|
```
|
|
|
|
### Example: Full Webhook Processing
|
|
|
|
```javascript
|
|
// Get webhook data from previous node
|
|
const webhookOutput = $input.first().json;
|
|
|
|
// Access the actual payload
|
|
const payload = webhookOutput.body;
|
|
|
|
// Access headers if needed
|
|
const contentType = webhookOutput.headers['content-type'];
|
|
|
|
// Access query parameters if needed
|
|
const apiKey = webhookOutput.query.api_key;
|
|
|
|
// Process the actual data
|
|
return [{
|
|
json: {
|
|
// Data from webhook body
|
|
userName: payload.name,
|
|
userEmail: payload.email,
|
|
message: payload.message,
|
|
|
|
// Metadata
|
|
receivedAt: new Date().toISOString(),
|
|
contentType: contentType,
|
|
authenticated: !!apiKey
|
|
}
|
|
}];
|
|
```
|
|
|
|
### POST Data, Query Params, and Headers
|
|
|
|
```javascript
|
|
const webhook = $input.first().json;
|
|
|
|
return [{
|
|
json: {
|
|
// POST body data
|
|
formData: webhook.body,
|
|
|
|
// Query parameters (?key=value)
|
|
queryParams: webhook.query,
|
|
|
|
// HTTP headers
|
|
userAgent: webhook.headers['user-agent'],
|
|
contentType: webhook.headers['content-type'],
|
|
|
|
// Request metadata
|
|
method: webhook.method, // POST, GET, etc.
|
|
url: webhook.url
|
|
}
|
|
}];
|
|
```
|
|
|
|
### Common Webhook Scenarios
|
|
|
|
```javascript
|
|
// Scenario 1: Form submission
|
|
const formData = $json.body;
|
|
const name = formData.name;
|
|
const email = formData.email;
|
|
|
|
// Scenario 2: JSON API webhook
|
|
const apiPayload = $json.body;
|
|
const eventType = apiPayload.event;
|
|
const data = apiPayload.data;
|
|
|
|
// Scenario 3: Query parameters
|
|
const apiKey = $json.query.api_key;
|
|
const userId = $json.query.user_id;
|
|
|
|
// Scenario 4: Headers
|
|
const authorization = $json.headers['authorization'];
|
|
const signature = $json.headers['x-signature'];
|
|
```
|
|
|
|
---
|
|
|
|
## Choosing the Right Pattern
|
|
|
|
### Decision Tree
|
|
|
|
```
|
|
Do you need ALL items from previous node?
|
|
├─ YES → Use $input.all()
|
|
│
|
|
└─ NO → Do you need just the FIRST item?
|
|
├─ YES → Use $input.first()
|
|
│
|
|
└─ NO → Are you in "Each Item" mode?
|
|
├─ YES → Use $input.item
|
|
│
|
|
└─ NO → Do you need specific node data?
|
|
├─ YES → Use $node["NodeName"]
|
|
└─ NO → Use $input.first() (default)
|
|
```
|
|
|
|
### Quick Reference Table
|
|
|
|
| Scenario | Use This | Example |
|
|
|----------|----------|---------|
|
|
| Sum all amounts | `$input.all()` | `allItems.reduce((sum, i) => sum + i.json.amount, 0)` |
|
|
| Get API response | `$input.first()` | `$input.first().json.data` |
|
|
| Process each independently | `$input.item` | `$input.item.json` (Each Item mode) |
|
|
| Combine two nodes | `$node["Name"]` | `$node["API"].json` |
|
|
| Filter array | `$input.all()` | `allItems.filter(i => i.json.active)` |
|
|
| Transform single object | `$input.first()` | `{...input.first().json, new: true}` |
|
|
| Webhook data | `$input.first()` | `$input.first().json.body` |
|
|
|
|
---
|
|
|
|
## Common Mistakes
|
|
|
|
### Mistake 1: Using $json Without Context
|
|
|
|
```javascript
|
|
// ❌ WRONG: $json is ambiguous
|
|
const value = $json.field;
|
|
|
|
// ✅ CORRECT: Be explicit
|
|
const value = $input.first().json.field;
|
|
```
|
|
|
|
### Mistake 2: Forgetting .json Property
|
|
|
|
```javascript
|
|
// ❌ WRONG: Trying to access fields on item object
|
|
const items = $input.all();
|
|
const names = items.map(item => item.name); // undefined
|
|
|
|
// ✅ CORRECT: Access via .json
|
|
const names = items.map(item => item.json.name);
|
|
```
|
|
|
|
### Mistake 3: Using $input.item in All Items Mode
|
|
|
|
```javascript
|
|
// ❌ WRONG: $input.item is undefined in "All Items" mode
|
|
const data = $input.item.json; // Error!
|
|
|
|
// ✅ CORRECT: Use appropriate method
|
|
const data = $input.first().json; // Or $input.all()
|
|
```
|
|
|
|
### Mistake 4: Not Handling Empty Arrays
|
|
|
|
```javascript
|
|
// ❌ WRONG: Crashes if no items
|
|
const first = $input.all()[0].json;
|
|
|
|
// ✅ CORRECT: Check length first
|
|
const items = $input.all();
|
|
if (items.length === 0) {
|
|
return [];
|
|
}
|
|
const first = items[0].json;
|
|
|
|
// ✅ ALSO CORRECT: Use $input.first()
|
|
const first = $input.first().json; // Built-in safety
|
|
```
|
|
|
|
### Mistake 5: Modifying Original Data
|
|
|
|
```javascript
|
|
// ❌ RISKY: Mutating original
|
|
const items = $input.all();
|
|
items[0].json.modified = true; // Modifies original
|
|
return items;
|
|
|
|
// ✅ SAFE: Create new objects
|
|
const items = $input.all();
|
|
return items.map(item => ({
|
|
json: {
|
|
...item.json,
|
|
modified: true
|
|
}
|
|
}));
|
|
```
|
|
|
|
---
|
|
|
|
## Advanced Patterns
|
|
|
|
### Pattern: Pagination Handling
|
|
|
|
```javascript
|
|
const currentPage = $input.all();
|
|
const pageNumber = $node["Set Page"].json.page || 1;
|
|
|
|
// Combine with previous pages
|
|
const allPreviousPages = $node["Accumulator"]?.json.accumulated || [];
|
|
|
|
return [{
|
|
json: {
|
|
accumulated: [...allPreviousPages, ...currentPage],
|
|
currentPage: pageNumber,
|
|
totalItems: allPreviousPages.length + currentPage.length
|
|
}
|
|
}];
|
|
```
|
|
|
|
### Pattern: Conditional Node Reference
|
|
|
|
```javascript
|
|
// Access different nodes based on condition
|
|
const condition = $input.first().json.type;
|
|
|
|
let data;
|
|
if (condition === 'api') {
|
|
data = $node["API Response"].json;
|
|
} else if (condition === 'database') {
|
|
data = $node["Database"].json;
|
|
} else {
|
|
data = $node["Default"].json;
|
|
}
|
|
|
|
return [{json: data}];
|
|
```
|
|
|
|
### Pattern: Multi-Node Aggregation
|
|
|
|
```javascript
|
|
// Collect data from multiple named nodes
|
|
const sources = ['Source1', 'Source2', 'Source3'];
|
|
const allData = [];
|
|
|
|
for (const source of sources) {
|
|
const nodeData = $node[source]?.json;
|
|
if (nodeData) {
|
|
allData.push({
|
|
source,
|
|
data: nodeData
|
|
});
|
|
}
|
|
}
|
|
|
|
return allData.map(item => ({json: item}));
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
**Most Common Patterns**:
|
|
1. `$input.all()` - Process multiple items, batch operations
|
|
2. `$input.first()` - Single item, API responses
|
|
3. `$input.item` - Each Item mode processing
|
|
|
|
**Critical Rule**:
|
|
- Webhook data is under `.body` property
|
|
|
|
**Best Practice**:
|
|
- Be explicit: Use `$input.first().json.field` instead of `$json.field`
|
|
- Always check for null/undefined
|
|
- Use appropriate method for your mode (All Items vs Each Item)
|
|
|
|
**See Also**:
|
|
- [SKILL.md](SKILL.md) - Overview and quick start
|
|
- [COMMON_PATTERNS.md](COMMON_PATTERNS.md) - Production patterns
|
|
- [ERROR_PATTERNS.md](ERROR_PATTERNS.md) - Avoid common mistakes
|