The pipe operator (|) is a QuivaWorks-specific extension that enables powerful string concatenation and handling of missing values. It’s one of the most frequently used features for building dynamic content.
The pipe operator is not part of standard JSONPath. It’s a custom QuivaWorks feature designed to make string building easier and more intuitive.
How It Works
The pipe operator splits your expression into segments and concatenates them:
segment1|segment2|segment3
Static text segments are included as-is
Variable mappings (starting with $.) are evaluated and inserted
All segments are converted to strings and joined together
Important: The pipe operator forces string conversion of all values. This means objects, arrays, and primitives are all converted to strings during concatenation.
{
"message" : "Hello |$.user.name|, welcome back!"
}
Pipe Operator with Different Data Types
The trailing pipe (|) has special behavior depending on the data type:
Strings & Primitives
Arrays
Objects
Missing Data
{
"withPipe" : "$.user.name|" ,
"withoutPipe" : "$.user.name" ,
"numberWithPipe" : "$.user.age|" ,
"numberWithoutPipe" : "$.user.age" ,
"booleanWithPipe" : "$.user.active|" ,
"booleanWithoutPipe" : "$.user.active"
}
{
"user" : {
"name" : "Alice" ,
"age" : 30 ,
"active" : true
}
}
{
"withPipe" : "Alice" ,
"withoutPipe" : "Alice" ,
"numberWithPipe" : "30" ,
"numberWithoutPipe" : 30 ,
"booleanWithPipe" : "true" ,
"booleanWithoutPipe" : true
}
For primitive values (strings, numbers, booleans), the pipe converts them all into strings. {
"withPipe" : "$.user.tags|" ,
"withoutPipe" : "$.user.tags"
}
{
"user" : {
"tags" : [ "premium" , "verified" , "active" ]
}
}
{
"withPipe" : "premium,verified,active" ,
"withoutPipe" : [ "premium" , "verified" , "active" ]
}
With pipe: Array is joined into a comma-separated string
Without pipe: Array is preserved as-is{
"withPipe" : "$.user.address|" ,
"withoutPipe" : "$.user.address"
}
{
"user" : {
"address" : {
"street" : "123 Main St" ,
"city" : "Boston" ,
"state" : "MA"
}
}
}
{
"withPipe" : "[object Object]" ,
"withoutPipe" : {
"street" : "123 Main St" ,
"city" : "Boston" ,
"state" : "MA"
}
}
With pipe: Object is converted to "[object Object]" string
Without pipe: Object is preserved as-is{
"withPipe" : "$.user.phone|" ,
"withoutPipe" : "$.user.phone"
}
With Data (missing property)
{
"user" : {
"name" : "Alice"
}
}
With pipe: Property exists with empty string
Without pipe: Property is removed entirely from output
Key Behaviors:
Data Type Without Pipe $.path With Pipe $.path| String/Number/Boolean Value as-is Value as string Array Full array Comma-separated string Object Full object "[object Object]"Missing Property removed Empty string ""
The pipe operator always forces string conversion , so use it carefully with objects and arrays.
Working with Arrays
The pipe operator provides convenient ways to work with array data.
Join Array Elements
Custom Separators
Preserve Array Structure
{
"commaSeparated" : "$.order.items|" ,
"withLabel" : "Items: |$.order.items|"
}
{
"order" : {
"items" : [ "Widget" , "Gadget" , "Doohickey" ]
}
}
{
"commaSeparated" : "Widget,Gadget,Doohickey" ,
"withLabel" : "Items: Widget,Gadget,Doohickey"
}
Arrays are automatically joined with commas. {
"pipeSeparated" : "$.tags[0]| | |$.tags[1]| | |$.tags[2]|" ,
"listFormat" : "Tags: |$.tags[0]|, |$.tags[1]|, and |$.tags[2]|"
}
{
"tags" : [ "urgent" , "important" , "review" ]
}
{
"pipeSeparated" : "urgent | important | review" ,
"listFormat" : "Tags: urgent, important, and review"
}
For custom formatting, access array elements individually. {
"asArray" : "$.order.items" ,
"asString" : "$.order.items|"
}
{
"order" : {
"items" : [
{ "name" : "Widget" , "price" : 29.99 },
{ "name" : "Gadget" , "price" : 49.99 }
]
}
}
{
"asArray" : [
{ "name" : "Widget" , "price" : 29.99 },
{ "name" : "Gadget" , "price" : 49.99 }
],
"asString" : "[object Object],[object Object]"
}
Without pipe: Array structure is preserved
With pipe: Array of objects becomes unhelpful string
When to use pipe with arrays:
✅ Simple arrays of strings or numbers: ["a", "b", "c"] → "a,b,c"
✅ When you want a comma-separated list
❌ Arrays of objects - will result in "[object Object],[object Object]"
❌ When you need to preserve array structure for further processing
Working with Objects
When working with nested objects, be careful with the pipe operator.
{
"street" : "$.user.address.street|" ,
"city" : "$.user.address.city|" ,
"fullAddress" : "$.user.address.street|, |$.user.address.city|, |$.user.address.state|"
}
{
"user" : {
"address" : {
"street" : "123 Main St" ,
"city" : "Boston" ,
"state" : "MA"
}
}
}
{
"street" : "123 Main St" ,
"city" : "Boston" ,
"fullAddress" : "123 Main St, Boston, MA"
}
✅ Access nested properties individually for concatenation. {
"addressObject" : "$.user.address" ,
"userProfile" : "$.user.profile"
}
{
"user" : {
"address" : {
"street" : "123 Main St" ,
"city" : "Boston"
},
"profile" : {
"bio" : "Developer" ,
"avatar" : "avatar.jpg"
}
}
}
{
"addressObject" : {
"street" : "123 Main St" ,
"city" : "Boston"
},
"userProfile" : {
"bio" : "Developer" ,
"avatar" : "avatar.jpg"
}
}
✅ Omit pipe to preserve full object structure. {
"address" : "$.user.address|"
}
{
"user" : {
"address" : {
"street" : "123 Main St" ,
"city" : "Boston"
}
}
}
{
"address" : "[object Object]"
}
❌ Don’t use pipe with objects - you’ll get "[object Object]".
Common Mistake: // ❌ Wrong - will return "[object Object]"
{ "address" : "$.user.address|" }
// ✅ Correct - preserves object
{ "address" : "$.user.address" }
// ✅ Correct - access specific properties
{ "address" : "$.user.address.street|, |$.user.address.city|" }
Combine static text with dynamic values from your flow.
Simple Text
Multiple Variables
No Static Text
{
"greeting" : "Hello |$.fetch_user.name|!" ,
"message" : "Your order |$.trigger.order_id| is ready" ,
"status" : "Processing order for |$.customer.email|"
}
{
"trigger" : { "order_id" : "ORD-123" },
"fetch_user" : { "name" : "Alice" },
"customer" : { "email" : "[email protected] " }
}
{
"greeting" : "Hello Alice!" ,
"message" : "Your order ORD-123 is ready" ,
"status" : "Processing order for [email protected] "
}
{
"subject" : "Order |$.trigger.order_id| for |$.customer.name|" ,
"summary" : "User |$.user.id| (|$.user.email|) placed order |$.order.id|"
}
{
"trigger" : { "order_id" : "ORD-456" },
"customer" : { "name" : "Bob" },
"user" : { "id" : "USR-789" , "email" : "[email protected] " },
"order" : { "id" : "ORD-456" }
}
{
"subject" : "Order ORD-456 for Bob" ,
"summary" : "User USR-789 ([email protected] ) placed order ORD-456"
}
{
"fullName" : "$.user.firstName| |$.user.lastName" ,
"address" : "$.address.street|, |$.address.city|, |$.address.state"
}
{
"user" : { "firstName" : "Alice" , "lastName" : "Smith" },
"address" : { "street" : "123 Main St" , "city" : "Boston" , "state" : "MA" }
}
{
"fullName" : "Alice Smith" ,
"address" : "123 Main St, Boston, MA"
}
The pipe operator automatically converts all values to strings during concatenation, so you can mix numbers, booleans, and strings freely.
Joining Values
Use the pipe operator with a separator to join multiple values.
Space Separator
Custom Separators
Multi-line Content
{
"fullName" : "$.user.firstName| |$.user.lastName" ,
"fullAddress" : "$.user.title| |$.user.firstName| |$.user.lastName"
}
{
"fullName" : "Alice Smith" ,
"fullAddress" : "Dr. Alice Smith"
}
Each | | adds a space between values. {
"commaSeparated" : "$.a|, |$.b|, |$.c" ,
"pipeSeparated" : "$.x| | |$.y| | |$.z" ,
"hyphenated" : "$.date.year|-|$.date.month|-|$.date.day"
}
{
"a" : "Apple" , "b" : "Banana" , "c" : "Cherry" ,
"x" : "1" , "y" : "2" , "z" : "3" ,
"date" : { "year" : "2025" , "month" : "01" , "day" : "15" }
}
{
"commaSeparated" : "Apple, Banana, Cherry" ,
"pipeSeparated" : "1 | 2 | 3" ,
"hyphenated" : "2025-01-15"
}
{
"emailBody" : "Dear |$.customer.name|,
Your order |$.order.id| has been shipped.
Tracking: |$.shipping.tracking|
Thank you!"
}
You can include line breaks in your static text segments.
Handling Missing Values
The pipe operator provides elegant solutions for handling missing or undefined data.
Empty String Default
Default Text
Without Pipe Operator
{
"phone" : "$.user.phone|" ,
"address" : "$.user.address|" ,
"notes" : "$.order.notes|"
}
With Data (missing properties)
{
"user" : {
"name" : "Alice"
},
"order" : {
"id" : "ORD-123"
}
}
{
"phone" : "" ,
"address" : "" ,
"notes" : ""
}
Trailing pipe ensures properties exist with empty strings instead of being removed. {
"status" : "Status: |$.order.status|" ,
"note" : "Note: |$.order.note|" ,
"priority" : "Priority: |$.task.priority|"
}
With Data (missing some values)
{
"order" : {
"status" : "shipped"
},
"task" : {}
}
{
"status" : "Status: shipped" ,
"note" : "Note: " ,
"priority" : "Priority: "
}
When a value is missing, only the static text remains. {
"phone" : "$.user.phone" ,
"address" : "$.user.address"
}
With Data (missing properties)
{
"user" : {
"name" : "Alice"
}
}
Result (properties removed)
Without the pipe operator, missing properties are removed entirely from the output.
Without a trailing pipe: { "value" : "$.missing.property" } // Property removed from output
With a trailing pipe: { "value" : "$.missing.property|" } // Returns: ""
The trailing pipe ensures the property exists in the output with an empty string value instead of being removed.
Building URLs and Endpoints
Construct dynamic URLs using the pipe operator.
API Endpoints
File Paths
Query Parameters
{
"userEndpoint" : "https://api.example.com/users/|$.trigger.user_id|" ,
"orderEndpoint" : "https://api.example.com/orders/|$.trigger.order_id|/details" ,
"searchUrl" : "https://example.com/search?q=|$.query.term|&limit=|$.query.limit|"
}
{
"trigger" : {
"user_id" : "USR-789" ,
"order_id" : "ORD-456"
},
"query" : {
"term" : "laptop" ,
"limit" : 10
}
}
{
"userEndpoint" : "https://api.example.com/users/USR-789" ,
"orderEndpoint" : "https://api.example.com/orders/ORD-456/details" ,
"searchUrl" : "https://example.com/search?q=laptop&limit=10"
}
{
"avatarUrl" : "https://cdn.example.com/avatars/|$.user.id|/|$.user.avatar|" ,
"documentPath" : "/documents/|$.company.id|/|$.document.year|/|$.document.id|.pdf" ,
"imagePath" : "images/|$.product.category|/|$.product.id|/thumbnail.jpg"
}
{
"user" : { "id" : "USR-123" , "avatar" : "profile.jpg" },
"company" : { "id" : "COMP-456" },
"document" : { "year" : "2025" , "id" : "DOC-789" },
"product" : { "category" : "electronics" , "id" : "PROD-001" }
}
{
"avatarUrl" : "https://cdn.example.com/avatars/USR-123/profile.jpg" ,
"documentPath" : "/documents/COMP-456/2025/DOC-789.pdf" ,
"imagePath" : "images/electronics/PROD-001/thumbnail.jpg"
}
{
"webhookUrl" : "https://hooks.example.com/webhook?event=|$.trigger.event|&id=|$.trigger.id|×tamp=|$.trigger.timestamp|"
}
{
"trigger" : {
"event" : "order.created" ,
"id" : "EVT-123" ,
"timestamp" : "2025-01-15T10:30:00Z"
}
}
{
"webhookUrl" : "https://hooks.example.com/webhook?event=order.created&id=EVT-123×tamp=2025-01-15T10:30:00Z"
}
Email and Message Templates
Create personalized email content and notifications.
Email Subject Lines
Email Bodies
SMS Messages
Push Notifications
{
"orderConfirmation" : "Order |$.order.id| Confirmed - Thank you |$.customer.firstName|!" ,
"shipmentNotice" : "Your order |$.order.id| has shipped!" ,
"invoice" : "Invoice #|$.invoice.number| for |$.customer.company|"
}
{
"body" : "Dear |$.customer.firstName| |$.customer.lastName|,
Thank you for your order #|$.order.id|!
Order Details:
- Items: |$.order.itemCount|
- Total: $|$.order.total|
- Status: |$.order.status|
Tracking Number: |$.shipping.tracking|
Questions? Reply to this email or call |$.support.phone|.
Best regards,
|$.company.name| Team"
}
{
"sms" : "Hi |$.customer.firstName|! Your order |$.order.id| ($|$.order.total|) has shipped. Track: |$.tracking.url|"
}
Keep it concise for SMS with character limits. {
"title" : "Order Update" ,
"body" : "|$.order.status| - Order |$.order.id|" ,
"data" : {
"orderId" : "$.order.id|" ,
"status" : "$.order.status|"
}
}
Complex Concatenation
Combine multiple data sources and formats.
JSONPath with Pipe Operator
Combine JSONPath queries with string concatenation.
Array Properties
Nested Access
With Filters
{
"firstItem" : "First item: |$.items[0].name| ($|$.items[0].price|)" ,
"lastItem" : "Last item: |$.items[-1].name| ($|$.items[-1].price|)"
}
{
"userInfo" : "User |$.response.data.user.profile.name| (|$.response.data.user.email|)" ,
"location" : "Located in |$.response.data.user.address.city|, |$.response.data.user.address.country|"
}
{
"premiumUsers" : "Premium users: |$.users[?(@.tier===premium)].length|" ,
"totalRevenue" : "Total: $|$.orders[?(@.status===completed)].total|"
}
Note: This combines JSONPath filters with string concatenation.
Common Patterns
{
"fullName" : "$.user.firstName| |$.user.lastName" ,
"displayName" : "$.user.title| |$.user.firstName| |$.user.lastName|, |$.user.suffix|"
}
{
"fullAddress" : "$.address.street|, |$.address.city|, |$.address.state| |$.address.zip|" ,
"singleLine" : "$.address.street| |$.address.city| |$.address.state| |$.address.zip|"
}
{
"summary" : "Order contains: |$.items[0].name|, |$.items[1].name|, and |$.items[2].name|" ,
"count" : "Total items: |$.items.length|" ,
"simpleList" : "Tags: |$.tags|"
}
Note: Use $.tags| to join simple arrays. For arrays of objects, access individual items.
{
"tagsAsArray" : "$.product.tags" ,
"tagsAsString" : "$.product.tags|" ,
"customFormat" : "Tags: |$.product.tags[0]|, |$.product.tags[1]|, |$.product.tags[2]|"
}
Choose based on whether you need array structure or a readable string.
Best Practices
Use Trailing Pipes for Optional Fields
Add | at the end when you want to ensure empty strings instead of undefined: { "optional" : "$.user.middleName|" }
Be Consistent with Spacing
Choose a spacing style and stick with it: {
"withSpaces" : "$.a| |$.b| |$.c" ,
"withCommas" : "$.a|, |$.b|, |$.c|"
}
Handle Missing Data Gracefully
Structure your templates to work even when optional data is missing: {
"message" : "Status: |$.status||. |$.details||"
}
Test with Missing Values
Always test your pipe expressions with partial data to ensure they degrade gracefully.
Keep It Readable
For complex concatenations, consider breaking into multiple fields: {
"firstName" : "$.user.firstName|" ,
"lastName" : "$.user.lastName|" ,
"fullName" : "$.user.firstName| |$.user.lastName|"
}
Efficient Simple concatenations with few variables:
Less Efficient Complex expressions with many variables: "|$.a||$.b||$.c||$.d||$.e||$.f|"
The pipe operator is evaluated during mapping, so there’s minimal performance impact. However, excessively long concatenations with dozens of variables may be better split into multiple fields.
Debugging Tips
If parts of your string are missing:
Check that the source path exists
Verify the node has executed
Use the Flow tester to inspect data
Add trailing pipes to see empty strings: $.value|
If concatenation doesn’t work as expected:
Verify $. prefix on all variable paths
Check for typos in node IDs or property names
Test each variable separately first
Ensure all segments are separated by |
If you need a literal pipe character in your output: Unfortunately, there’s no escape for literal | in pipe expressions. Consider:
Using a different character
Building the string in multiple steps
Using a static field with the pipe character
If you’re seeing "[object Object]" in your output: Cause: You’re using a trailing pipe with an object:{ "address" : "$.user.address|" } // ❌ Returns "[object Object]"
Solutions:
Remove the pipe to preserve the object:
{ "address" : "$.user.address" } // ✅ Returns full object
Access individual properties:
{ "address" : "$.user.address.street|, |$.user.address.city|" }
Unexpected Array Behavior
If your array is appearing as a comma-separated string: Cause: You’re using a trailing pipe with an array:{ "items" : "$.order.items|" } // Returns "item1,item2,item3"
Solutions:
Remove pipe to preserve array: {"items": "$.order.items"}
Keep pipe if you want comma-separated string
Use JSONPath to select specific items: $.order.items[0]
What’s Next?