Skip to content

Data Mapping

Data mapping defines how information transforms as it flows between nodes. Master this to build powerful automations.

How Data Mapping Works

When you create an edge between nodes, you configure how source data maps to the target:

┌──────────────┐                    ┌──────────────┐
│   Node A     │      Edge          │   Node B     │
│              │   Mapping ──────▶  │              │
│  Output: {   │   Transform:       │  Input: {    │
│    user: {   │   userId: $.user.id│    userId:   │
│      id: 123 │   name: $.user.name│    name:     │
│      name:"" │                    │  }           │
│    }         │                    │              │
│  }           │                    │              │
└──────────────┘                    └──────────────┘

Basic Mapping

Pass-Through

Send all data unchanged:

yaml
data: "{{$}}"

The $ represents the entire source data.

Select Fields

Pick specific fields:

yaml
# Source: { user: { id: 123, name: "Alice", email: "a@b.com" } }

userId: "{{$.user.id}}"
userName: "{{$.user.name}}"

# Result: { userId: 123, userName: "Alice" }

Rename Fields

Map to different names:

yaml
# Source: { firstName: "Alice", lastName: "Smith" }

givenName: "{{$.firstName}}"
familyName: "{{$.lastName}}"

# Result: { givenName: "Alice", familyName: "Smith" }

Expression Syntax

Path Access

yaml
# Object property
value: "{{$.propertyName}}"

# Nested property
value: "{{$.parent.child.grandchild}}"

# Array element
value: "{{$.items[0]}}"

# Last array element
value: "{{$.items[-1]}}"

String Interpolation

Embed values in strings:

yaml
message: "Hello, {{$.userName}}!"
url: "https://api.example.com/users/{{$.userId}}"

Pure Expressions

Return typed values (not strings):

yaml
# Returns number, not string
count: {{$.items.length}}

# Returns boolean
enabled: {{$.status == "active"}}

# Returns object
user: {{$.data.user}}

Note: Without quotes, the value type is preserved. With quotes, it becomes a string.

Type Handling

Type Preservation

ExpressionResult Type
{{$.count}}number
"{{$.count}}"string
{{$.active}}boolean
"{{$.active}}"string
{{$.user}}object

Type Conversion

Convert between types:

yaml
# Number to string
countStr: "{{string($.count)}}"

# String to number (if valid)
amount: {{int($.priceStr)}}

# Boolean check
hasItems: {{$.items.length > 0}}

Transformations

String Operations

yaml
# Uppercase
upper: "{{upper($.name)}}"

# Lowercase
lower: "{{lower($.name)}}"

# Concatenation
full: "{{$.first}} {{$.last}}"

# Template
greeting: "Dear {{$.title}} {{$.lastName}},"

Numeric Operations

yaml
# Math
total: {{$.price * $.quantity}}
discount: {{$.total * 0.1}}
final: {{$.total - $.discount}}

# Rounding
rounded: {{round($.value)}}

Date Operations

yaml
# Current time
timestamp: "{{now()}}"

# Format date
formatted: "{{RFC3339($.createdAt)}}"

Array Operations

yaml
# Array length
count: {{$.items.length}}

# First element
first: {{$.items[0]}}

# Filter (conceptual)
active: {{filter($.users, "status == 'active'")}}

Object Mapping

Restructure Objects

yaml
# Source:
# {
#   data: {
#     user: { id: 1, name: "Alice" },
#     meta: { timestamp: "..." }
#   }
# }

# Target mapping:
user:
  id: "{{$.data.user.id}}"
  name: "{{$.data.user.name}}"
timestamp: "{{$.data.meta.timestamp}}"

# Result:
# {
#   user: { id: 1, name: "Alice" },
#   timestamp: "..."
# }

Nested Objects

yaml
response:
  success: true
  data:
    items: {{$.results}}
    count: {{$.results.length}}
  metadata:
    timestamp: "{{now()}}"

Spread/Merge

yaml
# Include all source fields plus new ones
...data: {{$}}
extraField: "added"

Conditional Mapping

Ternary Operator

yaml
status: "{{$.active ? 'enabled' : 'disabled'}}"
type: "{{$.amount > 100 ? 'large' : 'small'}}"

Default Values

yaml
# Use default if null/undefined
name: "{{$.name || 'Unknown'}}"
count: {{$.items.length || 0}}

Null Handling

yaml
# Check for existence
hasUser: {{$.user != null}}

# Safe navigation (if supported)
email: "{{$.user?.email || 'no-email'}}"

Common Patterns

HTTP Request Mapping

yaml
# Map incoming HTTP request
method: "{{$.method}}"
path: "{{$.path}}"
headers: {{$.headers}}
body: {{$.body}}
queryParams: {{$.queryParams}}
clientIP: "{{$.realIP}}"

HTTP Response Building

yaml
# Build response
body: |
  {
    "success": true,
    "data": {{$.result}},
    "timestamp": "{{now()}}"
  }
contentType: "application/json"
statusCode: 200

Event Transformation

yaml
# Transform webhook event
eventType: "{{$.type}}"
timestamp: "{{$.created_at}}"
source: "webhook"
data:
  id: "{{$.data.id}}"
  action: "{{$.action}}"

Database Record Mapping

yaml
# Map for database insert
record:
  id: "{{$.userId}}"
  email: "{{$.email}}"
  created_at: "{{now()}}"
  metadata: {{$.additionalInfo}}

Mapping Editor

Visual Mode

The edge properties panel provides visual mapping:

┌─────────────────────────────────────────────────┐
│ Data Mapping                                     │
├─────────────────────────────────────────────────┤
│ Target Schema          Source Expression        │
│ ┌────────────────┐    ┌────────────────────┐   │
│ │ userId         │ ←  │ $.user.id          │   │
│ └────────────────┘    └────────────────────┘   │
│ ┌────────────────┐    ┌────────────────────┐   │
│ │ email          │ ←  │ $.user.email       │   │
│ └────────────────┘    └────────────────────┘   │
│ ┌────────────────┐    ┌────────────────────┐   │
│ │ timestamp      │ ←  │ now()              │   │
│ └────────────────┘    └────────────────────┘   │
└─────────────────────────────────────────────────┘

Code Mode

Switch to YAML/JSON for complex mappings:

yaml
userId: "{{$.user.id}}"
email: "{{$.user.email}}"
profile:
  name: "{{$.user.firstName}} {{$.user.lastName}}"
  avatar: "{{$.user.avatarUrl || 'default.png'}}"
settings: {{$.user.preferences}}

Schema Hints

The editor shows expected schema:

Target expects:
{
  userId: number (required)
  email: string (required)
  profile: {
    name: string
    avatar: string
  }
}

Debugging Mappings

Use Debug Node

Add a Debug node to inspect data:

Source ──▶ Debug ──▶ Target

             └─▶ View actual data in logs

Check Types

Common type issues:

yaml
# ❌ Type mismatch - returns string
count: "{{$.items.length}}"

# ✅ Correct - returns number
count: {{$.items.length}}

Validate Paths

Test expressions:

  1. Add Debug node before mapping
  2. Check actual data structure
  3. Verify path exists
  4. Update mapping accordingly

Best Practices

1. Start Simple

Begin with basic mappings, then refine:

yaml
# Start with
data: {{$}}

# Then refine to
userId: "{{$.user.id}}"

2. Handle Missing Data

Always consider null/undefined:

yaml
email: "{{$.email || 'not-provided'}}"
count: {{$.items.length || 0}}

3. Document Complex Mappings

Add comments for clarity:

yaml
# Extract user for notification service
userId: "{{$.data.user.id}}"
# Format full name from parts
fullName: "{{$.data.user.first}} {{$.data.user.last}}"
# Use current timestamp if not provided
timestamp: "{{$.timestamp || now()}}"

4. Test Edge Cases

Verify mappings handle:

  • Empty arrays
  • Null values
  • Missing fields
  • Unexpected types

Next Steps

Build flow-based applications on Kubernetes