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": "alice@example.com",
  "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": "user@example.com",
        "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": "alice@example.com"
  }
}
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?

JSONPath Features

Learn advanced selectors like wildcards, slicing, and recursive descent

Pipe Operator

Build dynamic strings and provide fallback values

Examples

See real-world variable mapping patterns

Reference

Quick syntax lookup and troubleshooting
Questions? Check the Reference for troubleshooting or visit our Help Center.