Data Flow Patterns

Visual examples showing how data flows through workflows using expressions

Common Data Flow Patterns

Visual examples of how data moves through your workflows. These patterns solve common real-world problems.

Pattern 1: Simple Pass-Through

Use when: You get data, transform it slightly, and pass it forward.

Webhook Trigger: { userId: "123" }
         ↓
┌─────────────────────┐
│  Fetch User Node    │
│  URL: /users/       │
│       {{ $trigger.userId }}
└─────────────────────┘
         ↓
Output: { id: "123", name: "Alice", email: "alice@example.com" }
         ↓
┌─────────────────────┐
│  Send Email Node    │
│  To: {{ $node["Fetch User"].json.email }}
│  Body: Hi {{ $node["Fetch User"].json.name }}!
└─────────────────────┘

Key point: Each node takes input from trigger or previous nodes using {{ }} expressions.

Pattern 2: Data Aggregation

Use when: You need to combine data from multiple sources.

Config Node: { apiKey: "abc123", baseUrl: "https://api.example.com" }

Orders Node: [{ id: 1, total: 100 }, { id: 2, total: 200 }]

Users Node: [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]
        ↓↓↓ All connect here
┌─────────────────────┐
│  Merge Node         │
│  Output: {          │
│    apiKey: {{ $node["Config"].json.apiKey }},
│    orders: {{ $node["Orders"].json }},
│    users: {{ $node["Users"].json }}
│  }
└─────────────────────┘

Real-world use: Collect data from multiple APIs, then send as one package.

Pattern 3: Conditional Routing

Use when: Different data takes different paths.

Fetch Order: { status: "pending", total: 1500 }
         ↓
┌─────────────────────┐
│  If Node            │
│  Condition:         │
│  {{ $node["Fetch Order"].json.total > 1000 }}
└─────┬───────────────┘
   ✓  │  ✗
      ├─ true: High Value Order
      │    ├→ Notify Manager
      │    ├→ Apply Premium Handling
      │    └→ Send Premium Email
      │
      └─ false: Standard Order
           ├→ Process Normally
           ├→ Send Standard Email
           └→ Archive

Real-world use: Expense approvals, order routing, priority handling.

Pattern 4: Array Processing

Use when: You have a list of items to process.

Fetch Products: [
  { id: 1, price: 50, inStock: true },
  { id: 2, price: 75, inStock: false },
  { id: 3, price: 100, inStock: true }
]
         ↓
┌─────────────────────┐
│  Filter Node        │
│  Keep only:         │
│  {{ $input.filter(p => p.inStock) }}
└─────────────────────┘
         ↓
[
  { id: 1, price: 50 },
  { id: 3, price: 100 }
]
         ↓
┌─────────────────────┐
│  Add Tax Node       │
│  {{ $input.map(p => ({
│    ...p,
│    taxAmount: p.price * 0.1,
│    total: p.price * 1.1
│  })) }}
└─────────────────────┘
         ↓
[
  { id: 1, price: 50, tax: 5, total: 55 },
  { id: 3, price: 100, tax: 10, total: 110 }
]

Real-world use: Process order items, filter results, calculate taxes/fees.

Pattern 5: Error Handling with Fallback

Use when: You need a backup if something fails.

Get Weather (with retry):
  - Attempt 1: Timeout ❌
  - Attempt 2: Timeout ❌
  - Attempt 3: Timeout ❌
  - Retries exhausted
         ↓
    Use fallback:
    { temp: null, condition: "unknown" }
         ↓
┌─────────────────────┐
│  Send Email Node    │
│  Body:              │
│  Weather: {{        │
│    $node["Get Weather"].json.condition ??"Unable to fetch weather"
│  }}
└─────────────────────┘
         ↓
Email: "Unable to fetch weather"

Real-world use: Gracefully handle API failures with defaults.

Pattern 6: Loop Processing

Use when: Repeat the same action for each item.

CSV: [email1@example.com, email2@example.com, email3@example.com]
         ↓
┌─────────────────────┐
│  Loop Node          │
│  For each email:    │
└─────────────────────┘
         ↓
    ┌─────────────────┐
    │  Send Email     │
    │  To: {{         
    │    $input       │ (this email in loop)
    │  }}
    │  Body: Hi!      │
    └─────────────────┘
         ↓
✅ Email sent to email1@example.com
✅ Email sent to email2@example.com
✅ Email sent to email3@example.com

Real-world use: Send emails to multiple people, update multiple records.

Pattern 7: AI-Powered Enrichment

Use when: Enhance data with AI insights.

Raw Data: { title: "New Product", description: "..." }
         ↓
┌─────────────────────┐
│  AI Agent Node      │
│  Prompt:            │
│  "Suggest 5 tags    │
│   for: {{           │
│   $node["Raw"].json.title
│   }}"
└─────────────────────┘
         ↓
AI Response: { tags: ["new", "product", "launch", ...] }
         ↓
┌─────────────────────┐
│  Merge Node         │
│  {{                 │
│    ...$node["Raw"].json,
│    tags: $node["AI"].json.tags
│  }}
└─────────────────────┘
         ↓
{ title: "...", description: "...", tags: [...] }

Real-world use: Add AI suggestions, categorization, summaries.

Pattern 8: Multi-Step Approval

Use when: Different people approve at different stages.

Expense Request: { amount: 5000, description: "..." }
         ↓
┌─────────────────────┐
│  Manager Approval   │
└─────┬───────────────┘
    ✅ Approved
      ↓
┌─────────────────────┐
│  Finance Approval   │
└─────┬───────────────┘
    ✅ Approved
      ↓
┌─────────────────────┐
│  Process Payment    │
└─────────────────────┘
         ↓
Payment processed!

(If rejected at any step → notify requester)

Real-world use: Multi-level approvals, compliance checks.

Debugging Data Flow

Tip 1: Use Logging Nodes

Add Debug nodes to see what data looks like at each step:

[API Node] → [Debug Node] → [Next Node]
                ↓
            Outputs:
            { userId: "123", status: "active" }

Tip 2: Test with Sample Data

Before running live, test with known data:

Test Data:
  input: { amount: 100 }

Expected Output:
  { amount: 100, tax: 10, total: 110 }

Actual Output:
  { amount: 100, tax: 10, total: 110 }
   Match!

Tip 3: Build Incrementally

Don't build the entire workflow at once. Add nodes one at a time and test:

  1. Add trigger → test
  2. Add first node → test
  3. Add second node → test
  4. ...

Common Data Flow Mistakes

Mistake 1: Forgetting .json

❌ Wrong:

{{ $node["User"].name }}  // Missing .json!

✅ Correct:

{{ $node["User"].json.name }}

Mistake 2: Referencing a future node

❌ Wrong:

{{ $node["Process Results"].json }}  // This node hasn't run yet!

✅ Correct: Only reference nodes that appear BEFORE the current node.

Mistake 3: Not handling missing data

❌ Wrong:

{{ $node["User"].json.email }}  // What if email is missing?

✅ Correct:

{{ $node["User"].json.email ?? "no-email@example.com" }}

Next Steps

  • Diagram your workflow — Draw boxes for nodes, arrows for data
  • Identify the pattern — Which example above matches yours?
  • Test each node — Verify data at each step
  • Use Data Mapper — Don't type expressions manually
  • Add error handling — Include fallbacks for missing data

Related: