Variables
Application-scoped, dynamically-typed slots written by ports and pipeline components, readable from anywhere in the application — for loop counters, captured XPath fragments, configuration constants, and anything else that needs to outlive a single component.
A variable in Art2link ESB is a named value defined under an Application, alongside schemas and message types. It does not belong to any one message; it belongs to the application, and any port, pipeline component, map, or routing expression in that application can read it. Writing is restricted to two places: a port’s On Entry / On Exit assignment stages and a pipeline component returning a Variables dictionary on its output. Maps, custom functions, and other expression contexts are read-only with respect to variables.
Variables carry whatever the integration needs to remember between stages: a counter for a looping pipeline, a fragment captured out of the inbound payload by an XPath, a whole message held for later wrapping, an environment-specific endpoint, a correlation key. Anywhere a string can be typed in the platform, a variable can be referenced with {{Variable.Name}}.
A variable does not travel with the message. It lives at the application level, and every port and pipeline running inside that application sees the same variable. A receive port writing to CurrentBatchId and a send port reading {{Variable.CurrentBatchId}} a moment later are reading and writing the same slot.
This is the reverse of how promoted properties work. A promotion is a value lifted out of a specific message's payload; it is meaningful only while that message is being processed. A variable is a slot the application owns, and the runtime is what changes its value as messages move through ports.
Variables are scoped to an Application. From the application menu, the Variables list shows every variable defined in that application. Each entry has these fields:
| Field | Purpose |
|---|---|
| Name | How the variable is referenced everywhere else — in port assignments, pipeline component property values, map bindings, custom function arguments, and routing expressions. Names are flat under the Variable namespace, so they must be unique within the application. |
| Description | Free-text description for human readers. Optional, has no runtime effect. |
The list view follows the platform's Selected Application behavior — with an Application selected, the list filters to it and the Application column is hidden; with no selection, the list spans every Application you have access to and the column is shown. Creating a new variable uses the same flow: with an Application selected, the form opens with that Application preemptively chosen; without one, the form exposes an Application picker.
A variable does not declare a data type at design time. The runtime type is whatever the most recent assignment produced — the same slot can hold a string in one stage, a node-set in another, and a whole XML document in a third.
This mirrors the way promotions resolve: a variable can hold any of the same four shapes an XPath promotion can return, and the source of the assignment determines which one.
| Source of the assigned value | Variable holds |
|---|---|
| A literal string in the port's variable assignment UI | Scalar string |
| An XPath that resolves to a single text node or attribute | Scalar string |
| An XPath returning multiple matches | List (node-set) |
| An XPath returning an element with children | Sub-tree |
| An XPath of / | The whole message |
| A custom function call | Whatever the function returns |
A pipeline component setting {{Variable.Name}} directly | Whatever the component writes |
Downstream consumers see the value as it currently is. A map that reads {{Variable.CapturedHeader}} will receive a sub-tree if that is what was last written; passing the same token to a string-only context (a header name, a literal in a subscription expression) coerces to text.
Anywhere a string can be typed in the platform — subscription expressions, map bindings, pipeline component property values, custom function arguments, port adapter settings — you can reference a variable with a double-curly-brace token. The exception is naming an object itself: the Name field on a variable, port, message type, schema, etc. is a literal, never an expression.
| Token | Resolves to |
|---|---|
{{Variable.Name}} | The current value of the named variable in this application. Resolves to a scalar, list, sub-tree, or whole message depending on what was last written into the slot. |
The token grammar is identical to {{Promoted.Name}} and {{Message.MessageType}} — the same expression engine evaluates all three. A subscription expression that filters a send port to only pick up messages while a feature flag is on, for example, looks like this:
{{Message.MessageType}} == "Invoice" && {{Variable.InvoiceForwardingEnabled}} == "true"
A pipeline component can also write to a variable directly — setting {{Variable.Name}} from C# code — which is the canonical way to surface a derived value out of a pipeline for the rest of the flow to consume. See Pipeline components.
Declaring a variable allocates the slot. Giving it a value is the job of port variable assignments — an optional stage that runs immediately after the port has been entered and immediately before it is left. Every port type exposes the same two assignment points, regardless of how much else the port carries:
The two stages always sit at the edges of the port: On Entry runs immediately after the port is entered, before pipelines or maps; On Exit runs immediately before the port is left, after any pipelines and maps. A two-way port runs both stages on the request and again on the response, mirrored. The numbered stage diagrams in the Receive, Send, Loopback, and Null port articles show the full numbering for each direction.
{{Promoted.Name}} and {{Message.MessageType}} resolve to nothing. The same applies to the response side of a two-way send port. On Exit runs after the map, so by that stage the message has its post-map type and promotions are readable. See When a message type is instantiated.The port assignment stages are not the only writers. A pipeline component can also write variables by returning entries in the Variables dictionary on its PipelineComponentOutput — see Variables & Message Type in the component reference. The slot updated by a pipeline component write is the same slot the port assignment stages write to; downstream stages cannot tell which path produced the value.
{{Variable.Name}} freely and a custom function can take a variable value as an argument, but neither has a return path back into the variable slot. The only two writers are port assignment stages and pipeline components.Three patterns account for most of how variables are used in practice. The platform does not enforce any of these — a variable is just a slot — but the patterns are worth recognizing because they show up across most non-trivial integrations.
Pattern 1 — Loop counter. A pipeline component that splits a multi-record file into individual messages can use a variable as the running index, incrementing it on each iteration so downstream stages know which record they are looking at.
{{Variable.RecordIndex}}
The component reads the current value, uses it, increments it, writes it back. Because variables are application-scoped, the index is visible to every stage that runs while the loop is in flight — no need to thread the value through component arguments.
Pattern 2 — XPath capture. A common need is to lift a value out of the inbound message and reference it later in the flow, without making it part of the message type as a promotion. An On Entry variable assignment using an XPath does exactly that:
Variable.CorrelationId ← /Order/Header/CorrelationId/text()
From that point on, every stage in the application can reach {{Variable.CorrelationId}} — a send port can use it in a subscription expression, a map can drop it into the response, a pipeline component can log it. The value is a scalar string, because that is what the XPath returned.
Pattern 3 — Holding a fragment or a whole message. The same XPath assignment works for sub-trees and whole messages. To capture an entire envelope inbound for later wrapping:
Variable.OriginalRequest ← /
The variable now holds the whole message as a sub-tree. A two-way send port further along can drop {{Variable.OriginalRequest}} into an outbound envelope produced by a map, embedding the original payload verbatim. The same technique with a more selective XPath — /Order/ShipTo, say — captures just that branch.
Variables are evaluated lazily. The runtime does not pre-resolve every variable reference at the start of a flow; it resolves each {{Variable.Name}} token at the moment the stage that contains it executes.
| Stage | What happens |
|---|---|
| Slot allocation | When the application starts, every variable defined in it has a slot. Until something writes to a slot, reading it returns an empty value. |
| Assignment | A port On Entry / On Exit stage or a pipeline component writes a value. The slot's runtime type becomes whatever was just written. |
| Reference resolution | An expression containing {{Variable.Name}} is evaluated — the slot's current value is substituted in, with shape coercion if the consuming context demands a string. |
Because the slot is application-scoped, two messages flowing through the same application at the same time share it. Treat a variable as state owned by the integration, not state owned by the message — if per-message state is what you need, a promotion is the right tool.
Variables and promotions are two halves of the platform's expression model. Both are referenced with double-curly-brace tokens, both can resolve to scalar / list / sub-tree / whole-message shapes, and both are evaluated by the same expression engine.
The difference is whose state they describe. A promotion is a value derived from this message at the moment it enters the bus — bound to a message type, scoped to the lifetime of the message. A variable is a slot the application owns — bound to the application, written by ports and pipelines, visible to anything else running inside the application until something overwrites it.
When a routing or mapping expression needs both — a per-message value and an integration-state value — it can mix them freely. {{Message.MessageType}} == "Invoice" && {{Promoted.Currency}} == "USD" && {{Variable.RegionRoutingEnabled}} == "true" is a valid subscription expression that combines all three reference forms.
That is the whole job
Define a slot under the application, fill it from a port stage or a pipeline component, reference it as {{Variable.Name}} wherever you need it. Untyped, application-scoped, shared by every message in flight.