Skip to main content

Concatenation

A mapped string that contains a | is built rather than looked up. The engine splits the string on every |, resolves each segment that begins with $., leaves every other segment as literal text, and joins the pieces.
The result is always a string. | is the only reason to reach for this form — a mapping with no | never interpolates, so "Order $.trigger.id" stays exactly as written.
| is a delimiter, not an operator. Nothing may follow it but another segment. There is no | default(…), | upper, | join(", "), | number or | date(…), and segments cannot be chained into a pipeline. Every | you write adds a join point and nothing else.

The rules

A leading or trailing | produces an empty segment, which contributes nothing. That makes "$.a.b|" a way of forcing a value to a string. Line breaks inside a literal segment are kept, so a multi-line template works as written.

How each value is stringified

Objects and arrays are serialised as JSON, not as [object Object].
The last two rows are the distinction that catches people out. A single match holding an array is serialised as JSON; several matches are joined with commas.
The separator for multiple matches is always a comma. It cannot be changed.

Forcing a key to exist

A path that matches nothing resolves to [] and the key stays in the output holding an empty array. A trailing | turns that into "", which is usually easier to consume downstream.
against {"user": {"name": "Alice"}}:
Use it on fields you will render into text. Do not use it on a value a later step reads as a number, a list or an object — the trailing | converts all three to strings.

What it cannot do

$.order.status|pending does not fall back to "pending". Both segments are emitted, so with a status of shipped the result is "shippedpending", and with no status at all it is "pending" — right by accident in one case and wrong in the other.Put the default in the literal text instead, where the intent is visible: "Status: |$.order.status" yields "Status: shipped" or "Status: ". For a genuine fallback, use an Eval step.
$.customer.tier===premium|high|normal is not a conditional. It resolves to "highnormal" whatever the data holds — the first segment does not start with $., so it is emitted literally and evaluates nothing.Branching belongs in a Condition step or a Rules step.
Every | is a delimiter and there is no escape. $.x| | |$.y splits into five segments and produces "1 2" — two spaces, no bar. A pipe-separated string has to be assembled in an Eval step.
Because the split happens before JSONPath sees the string, a | inside a filter destroys it. $.users[?(@.role==='admin' || @.role==='moderator')] fails the step with Unexpected "?" at character 0. There is no way to write OR in a filter.

Next steps

JSONPath

Every selector and filter that works

Examples

Worked mappings, run end to end

Eval Step

Formatting, defaults and conditionals

Overview

Data sources, path shapes, failure modes