Skip to content
Art2link ESB v2.02 LTS HomeDocumentationBlogContact
Core concepts/Variables

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.

APPLICATION one shared bus, many messages in flight VARIABLE SLOT CurrentBatchId "B-2026-05-15-001" VARIABLE SLOT RegionRoutingEnabled "true" VARIABLE SLOT OriginalRequest <Order> … </Order> APPLICATION BUS — messages flow past, variables stay put MESSAGE #1 Type: Invoice PROMOTED CustomerId = "C-101" MESSAGE #2 Type: Invoice PROMOTED CustomerId = "C-208" MESSAGE #3 Type: Order PROMOTED OrderTotal = 1280

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.

Practical effect. Use a promotion when the value is intrinsic to a single message — a CustomerId on this Invoice, a Currency on that Order. Use a variable when the value is a piece of state the integration carries forward — a loop counter, a captured fragment for later use, or a configuration string set once and consumed many times.

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:

FieldPurpose
NameHow 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.
DescriptionFree-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.

No data type field. Variables are deliberately untyped at declaration time — the slot is allocated, but its shape is decided by what gets written into it. See Dynamic typing below.

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.

SCALAR Single value "true" Literal, scalar XPath, or function return Variable. FeatureFlag LIST Repeating values SKU-001 SKU-002 SKU-003 SKU-004 XPath node-set Variable. CapturedSKUs SUB-TREE Inner element <ShipTo> Name Street City Element + children Variable. ShipToFragment WHOLE MESSAGE Document root <Order> Header Lines ShipTo </Order> XPath of / Variable. OriginalRequest
Source of the assigned valueVariable holds
A literal string in the port's variable assignment UIScalar string
An XPath that resolves to a single text node or attributeScalar string
An XPath returning multiple matchesList (node-set)
An XPath returning an element with childrenSub-tree
An XPath of /The whole message
A custom function callWhatever the function returns
A pipeline component setting {{Variable.Name}} directlyWhatever 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.

TokenResolves 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:

EXPRESSIONsubscription on a send port
{{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:

PORT TYPE STAGES — Variables appear in every row Receive one-way request side STAGE 1 Adapter STAGE 2 Var On Entry STAGE 3 Pipeline STAGE 4 Map STAGE 5 Var On Exit → Bus Send one-way request side Bus → STAGE 1 Var On Entry STAGE 2 Map STAGE 3 Pipeline STAGE 4 Var On Exit STAGE 5 Adapter Loopback outbound from bus Bus → STAGE 1 Var On Entry STAGE 2 no map STAGE 3 no pipeline STAGE 4 Var On Exit → Bus Null discard sink Bus → STAGE 1 Var On Entry STAGE 2 no map STAGE 3 no pipeline STAGE 4 Var On Exit Discarded

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.

Variables are the only stage every port type has in common. Loopback and null ports do not run a pipeline or a map and have no adapter, but they still have On Entry and On Exit variable assignments — useful even when no other transformation is configured.
On Entry on a receive port runs before classification. The adapter releases the message without a type, so the only stage that sees it before the pipeline is Variable On Entry — and at that point no message type exists, no promotions have been evaluated, and {{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.

Maps and custom functions cannot write variables. A map can read {{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 i = 0 i = 1 i = 2 read, use, increment, write back {{Variable.RecordIndex}} visible to every stage in flight PATTERN 2 XPath capture <Order> Header CorrelationId Lines "X-2026-42" /Order/Header/ CorrelationId/text() PATTERN 3 Held fragment <Order> Header Lines </Order> VARIABLE OriginalRequest drop into a later envelope / — the whole message

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.

EXPRESSIONin a pipeline component / map binding
{{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:

XPATHport On Entry assignment
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:

XPATHport On Entry assignment
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.

StageWhat happens
Slot allocationWhen the application starts, every variable defined in it has a slot. Until something writes to a slot, reading it returns an empty value.
AssignmentA port On Entry / On Exit stage or a pipeline component writes a value. The slot's runtime type becomes whatever was just written.
Reference resolutionAn 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.



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.