Skip to main content

The Two Data Sources

QuivaWorks flows work with two primary data sources that you can reference using variable mapping:

Trigger Data

Data provided when the flow starts (webhooks, API calls, manual triggers)
$.trigger.property

Node Data

Output data from any previously executed node in your flow
$.NODE_ID.property

Core Syntax Pattern

The basic syntax follows this pattern:
$.source.property
  • $ - Root indicator (always required)
  • source - Either trigger or a NODE_ID
  • property - The data you want to access
The $. prefix tells QuivaWorks to evaluate this as a variable mapping expression rather than treating it as a literal string.

Accessing Trigger Data

Trigger data is available from the very first node in your flow. It contains the data provided when the flow was initiated.
When a webhook triggers your flow:
Webhook Payload
{
  "event": "order.created",
  "order_id": "ORD-12345",
  "user_id": "USR-789",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "total": 299.99,
    "items_count": 3
  }
}
Variable Mapping
{
  "eventType": "$.trigger.event",
  "orderId": "$.trigger.order_id",
  "userId": "$.trigger.user_id",
  "orderTotal": "$.trigger.data.total",
  "itemCount": "$.trigger.data.items_count"
}
Result:
{
  "eventType": "order.created",
  "orderId": "ORD-12345",
  "userId": "USR-789",
  "orderTotal": 299.99,
  "itemCount": 3
}
Trigger data is read-only throughout the flow. It contains the original input and doesn’t change as nodes execute.

Accessing Node Data

Node data becomes available after a node completes execution. You can reference any previously executed node using its unique ID.

Basic Node Access

{
  "id": "CUST-789",
  "firstName": "Alice",
  "lastName": "Smith",
  "email": "[email protected]",
  "phone": "+1-555-0123",
  "tier": "premium"
}

Execution Order Matters

1

Node 1: fetch_customer

Executes first. Can access $.trigger.* but no other nodes yet.
2

Node 2: fetch_orders

Can access $.trigger.* and $.fetch_customer.*
3

Node 3: send_email

Can access $.trigger.*, $.fetch_customer.*, and $.fetch_orders.*
You cannot reference a node that hasn’t executed yet. If Node 2 tries to access $.node_3.data, it will return undefined.

Nested Properties

Access deeply nested data using dot notation:
Data
{
  "fetch_user": {
    "profile": {
      "contact": {
        "email": "[email protected]",
        "phone": "+1-555-0100"
      }
    }
  }
}
Mapping
{
  "email": "$.fetch_user.profile.contact.email",
  "phone": "$.fetch_user.profile.contact.phone"
}

Array Access

Arrays use zero-based indexing (first element is at index 0).

Accessing Array Elements

{
  "fetch_orders": {
    "orders": [
      {"id": "ORD-001", "total": 99.99},
      {"id": "ORD-002", "total": 149.99},
      {"id": "ORD-003", "total": 79.99}
    ]
  }
}
Negative indexes count from the end:
  • [-1] = last item
  • [-2] = second to last item
  • [-3] = third to last item

Accessing Properties in Array Items

Data
{
  "products": [
    {"name": "Widget", "price": 29.99, "stock": 100},
    {"name": "Gadget", "price": 49.99, "stock": 50},
    {"name": "Doohickey", "price": 19.99, "stock": 200}
  ]
}
Mapping
{
  "firstProductName": "$.node.products[0].name",
  "firstProductPrice": "$.node.products[0].price",
  "lastProductStock": "$.node.products[-1].stock"
}
Result
{
  "firstProductName": "Widget",
  "firstProductPrice": 29.99,
  "lastProductStock": 200
}

Combining Trigger and Node Data

Real-world flows typically combine both data sources:
// Webhook triggers flow with order ID
// Flow fetches customer and order details
// Combines all data for email

Type Handling

Variable mapping preserves data types from the source:
Input
{
  "node": {
    "name": "Alice Smith",
    "status": "active"
  }
}
Mapping
{
  "userName": "$.node.name",
  "userStatus": "$.node.status"
}
Output (Strings)
{
  "userName": "Alice Smith",
  "userStatus": "active"
}

Handling Missing Data

When a path doesn’t exist, variable mapping removes the property from the output entirely:
{
  "node": {
    "name": "Alice",
    "email": "[email protected]"
  }
}
Properties with missing data are removed from the output entirely. They won’t appear as undefined or null - they simply won’t exist in the result object.
Use a trailing pipe to ensure missing values create the property with an empty string:
{
  "userPhone": "$.node.phone|",
  "userAddress": "$.node.address.street|"
}
Result:
{
  "userPhone": "",
  "userAddress": ""
}
This keeps the property in the output instead of removing it completely.Note: The trailing pipe also affects how arrays and objects are handled:
  • Arrays: Joined as comma-separated string (e.g., "a,b,c")
  • Objects: Converted to "[object Object]" (usually not desired)
Learn more about the pipe operator in Pipe Operator.

Type Handling with Pipe Operator

The trailing pipe (|) forces string conversion, which affects different data types:
Data
{
  "node": {
    "name": "Alice",
    "age": 30,
    "active": true
  }
}
Mapping
{
  "name": "$.node.name|",
  "age": "$.node.age|",
  "active": "$.node.active|"
}
Result (unchanged)
{
  "name": "Alice",
  "age": "30",
  "active": "true"
}
Strings, numbers, and booleans are all converted to strings with pipe.
Use trailing pipe carefully:
ScenarioRecommendation
Ensuring property exists when data might be missing✅ Use pipe: `$.value`
Simple array to comma-separated string✅ Use pipe: `$.tags`
Preserving array structure❌ No pipe: $.items
Preserving object structure❌ No pipe: $.user.address
Array of objects❌ No pipe: $.orders

Best Practices

Choose clear, meaningful node IDs that describe what the node does:
// ✅ Good - Clear and descriptive
{
  "email": "$.fetch_customer_data.email",
  "total": "$.calculate_order_total.amount"
}

// ❌ Avoid - Unclear and unmaintainable
{
  "email": "$.node_1.email",
  "total": "$.node_5.amount"
}
Only reference nodes that have already executed:
Flow Order:
1. trigger → available to all nodes
2. node_a → available to node_b, node_c
3. node_b → available to node_c only
4. node_c → not available to previous nodes
Use the Flow Debugger to verify node execution order.
Always plan for missing or null data. Use a trailing pipe to ensure empty strings:
{
  "email": "$.user.email|",
  "phone": "$.user.phone|",
  "address": "$.user.address|"
}
Or use static text concatenation for default messages:
{
  "status": "Status: |$.order.status|",
  "note": "Note: |$.order.note|"
}
Use the Flow Debugger to test mappings with actual data:
  1. Run your flow with test data
  2. Inspect each node’s output
  3. Verify mapping expressions return expected values
  4. Check for undefined or null values

Common Patterns

{
  "table": "orders",
  "data": {
    "order_id": "$.trigger.order_id",
    "customer_id": "$.trigger.customer_id",
    "created_at": "$.trigger.timestamp",
    "total": "$.trigger.data.total"
  }
}

Quick Syntax Reference

PatternDescriptionExample
$.trigger.propAccess trigger data$.trigger.order_id
$.node.propAccess node data$.fetch_user.email
$.node.nested.propNested property$.user.address.city
$.node.array[0]First array item$.orders[0]
$.node.array[-1]Last array item$.orders[-1]
$.node.array[0].propProperty in array$.orders[0].total

Try It Yourself

1

Create a Test Flow

Set up a simple flow with a manual trigger
2

Add Test Data

Provide sample JSON data in the trigger form
3

Add a Node

Create a node that uses variable mapping to access trigger data
4

Run and Debug

Execute the flow and inspect the results in the debugger

What’s Next?

Questions? Check the Reference for troubleshooting or visit our Help Center.