Ep 04: The Heartbeat of Data — Deep Dive into JSON Items & Implicit Loops

⏱ Est. reading time: 13 min Updated on 4/10/2026

Why This Is the Most Important Episode

Over 40% of beginner questions on the n8n community forum relate to data flow:

  • "Why did my node execute 5 times?"
  • "Why is the Merge node output wrong?"
  • "Why does $input.first() differ from $input.all() in Code nodes?"

The root cause is always: not understanding n8n's Item-Driven execution model.


Item Anatomy

All data in n8n travels between nodes as Items. An Item is a standard JSON object with two top-level fields:

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Complete structure of an n8n Data Item
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

const item = {
  // 📦 json: Core data carrier
  // All business data lives here
  "json": {
    "id": 42,
    "name": "Alice",
    "email": "[email protected]",
    "tags": ["VIP", "enterprise"],      // Arrays supported
    "address": {                         // Nested objects supported
      "city": "Shanghai",
      "district": "Pudong"
    }
  },
  
  // 📎 binary (optional): File carrier
  // Used when Items carry files (images, PDFs, CSVs)
  "binary": {
    "attachment": {
      "data": "base64_encoded_string",   // File content (Base64)
      "mimeType": "image/png",
      "fileName": "screenshot.png",
      "fileSize": 1024000
    }
  }
};

Items Are Arrays!

Critical: nodes don't pass single Items — they pass Item arrays:

graph LR
    subgraph "Node A Output"
        I1["Item 0
{name: 'Alice'}"] I2["Item 1
{name: 'Bob'}"] I3["Item 2
{name: 'Carol'}"] end subgraph "Connection" PIPE["📦📦📦
3 Items"] end subgraph "Node B Input" R1["Receives Item 0"] R2["Receives Item 1"] R3["Receives Item 2"] end I1 & I2 & I3 --> PIPE --> R1 & R2 & R3 style PIPE fill:#f59e0b,stroke:#d97706,color:#fff

The Implicit Loop

This is the biggest difference between n8n and traditional programming.

Traditional code requires manual loops:

# ❌ Traditional: manual iteration required
for user in users:
    result = process(user)
    results.append(result)

In n8n, loops are built-in — you don't even notice them:

sequenceDiagram
    participant HTTP as HTTP Request Node
    participant Set as Set Node
    participant Gmail as Gmail Node
    
    Note over HTTP: 🔄 API returns 3 users
    HTTP->>HTTP: Output: [{name:"Alice"}, {name:"Bob"}, {name:"Carol"}]
    
    rect rgb(40, 60, 40)
        Note over Set: 🔁 Implicit loop begins (no loop code visible!)
        HTTP->>Set: Item 0: {name:"Alice"}
        Set->>Set: Add field greeting: "Hello, Alice"
        HTTP->>Set: Item 1: {name:"Bob"}
        Set->>Set: Add field greeting: "Hello, Bob"
        HTTP->>Set: Item 2: {name:"Carol"}
        Set->>Set: Add field greeting: "Hello, Carol"
        Note over Set: 🔁 Implicit loop ends
    end
    
    Set->>Gmail: 3 Items with greetings
    Note over Gmail: Automatically sends 3 emails!

Golden Rule: Every n8n node executes once per Item by default. If upstream emits N Items, downstream executes N times. You write zero loop code.


Five Data Manipulation Patterns

Pattern 1: Split (1→N)

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Input: 1 Item containing an array of 3 orders
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

const orders = $input.first().json.orders;  // Extract the array

// Convert each array element into an independent Item
return orders.map(order => ({ json: order }));

// Output: 3 independent Items

Pattern 2: Aggregate (N→1)

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Input: 3 Items
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const allItems = $input.all();  // Get all upstream Items

const totalAmount = allItems.reduce(
  (sum, item) => sum + item.json.amount,
  0  // Initial value
);

// Return a single summary Item
return [{
  json: {
    totalOrders: allItems.length,
    totalAmount: totalAmount,
    averageAmount: Math.round(totalAmount / allItems.length)
  }
}];

Pattern 3: Map (N→N)

// Each Item processed independently, same count in = same count out
// Use $input.item to access the current Item in the implicit loop

const currentItem = $input.item;

return {
  json: {
    ...currentItem.json,                     // Spread: keep all existing fields
    processedAt: new Date().toISOString(),    // Add: processing timestamp
    nameLength: currentItem.json.name.length  // Add: computed field
  }
};

Pattern 4: Filter (N→M where M≤N)

const items = $input.all();

return items.filter(item => {
  // Keep only VIP users who spent over 1000
  return item.json.isVIP === true && item.json.totalSpent > 1000;
});
// Input: 10 Items → Output: maybe 3 Items

Pattern 5: Merge (Dual-stream join)

graph LR
    subgraph "Source 1: Users"
        U["[{id:1, name:'Alice'},
{id:2, name:'Bob'}]"] end subgraph "Source 2: Orders" O["[{userId:1, amount:99},
{userId:2, amount:150}]"] end U --> M["Merge Node
Mode: Combine by Field
Match: id = userId"] O --> M M --> R["[{id:1, name:'Alice', amount:99},
{id:2, name:'Bob', amount:150}]"] style M fill:#6366f1,stroke:#4f46e5,color:#fff

Common Pitfalls

Pitfall Symptom Cause Solution
Empty Items Downstream never executes Upstream output [] empty array Use If node: {{ $input.all().length > 0 }}
Over-execution Gmail sent 100 emails 100 Items flowed in Aggregate first, or use Limit node
Binary Lost File disappears after processing Code node didn't pass binary Return { json: {...}, binary: item.binary }
Nested JSON Can't access deep fields Wrong expression syntax Use {{ $json.address.city }} dot notation

Data Flow Lifecycle Summary

graph TB
    subgraph "n8n Item Lifecycle"
        Birth["🐣 Birth
Trigger produces initial Items"] Transform["🔄 Transform
Set/Code nodes modify fields"] Split["✂️ Split
1 Item → N Items"] Filter["🔍 Filter
N Items → M (M ≤ N)"] Merge["🔗 Merge
Multi-source Items JOIN"] Aggregate["📊 Aggregate
N Items → 1 summary"] Output["🎯 Consume
Gmail / Slack / DB nodes"] end Birth --> Transform --> Split --> Filter --> Merge --> Aggregate --> Output Transform -->|"Direct consume"| Output Filter -->|"Direct consume"| Output

Next Episode

In Ep 05, we put theory into practice — building a real Webhook + Telegram Bot bidirectional workflow to make data truly "flow".