Variable Visibility — Data Associations in BPMN
The Principle
Stateway never exposes all process variables to the frontend. Only variables explicitly declared as inputs of a task reach the user. This is controlled by the process designer through Data Associations — a native BPMN 2.0 feature.
Why This Matters
A credit approval process may have variables like credit_score, internal_fraud_flag, and stripe_customer_id that are for internal process use and must not be shown to the user. With Data Associations, the designer declares exactly what each role sees — and Stateway guarantees nothing else passes to the frontend.
How to Declare
In any userTask, add Data Inputs and Data Outputs using your BPMN modeler's properties panel. Data Inputs are variables that arrive pre-filled in the form. Data Outputs are the variables the user produces when completing the task.
<bpmn:userTask id="approveTask" name="Approve Request">
<bpmn:ioSpecification>
<!-- What the approver sees -->
<bpmn:dataInput id="in_amount" name="amount" />
<bpmn:dataInput id="in_justification" name="justification" />
<bpmn:dataInput id="in_requester" name="requesterName" />
<!-- What the approver fills in -->
<bpmn:dataOutput id="out_approved" name="approved" />
<bpmn:dataOutput id="out_comment" name="comment" />
</bpmn:ioSpecification>
</bpmn:userTask>
The SDK will deliver only amount, justification, and requesterName in the work.snapshot event. Variables like creditScore or internalNotes will not reach the frontend even if they exist in the process.
When the task is completed, through the SDK or the REST API, only approved and comment are written to the process instance. Submitted variables not declared as a dataOutput are discarded. An ioSpecification that declares no dataOutput at all writes nothing back to the instance. A userTask without an ioSpecification writes every submitted variable.
To store an output under a different process variable, add a dataOutputAssociation from the dataOutput to a data object reference. The value is then written under the data object's name instead of the output's name:
<bpmn:dataObject id="approvalFlagObj" name="approvalFlag" />
<bpmn:dataObjectReference id="approvalFlagRef" dataObjectRef="approvalFlagObj" />
<bpmn:userTask id="approveTask" name="Approve Request">
<bpmn:ioSpecification>
<bpmn:dataOutput id="out_approved" name="approved" />
</bpmn:ioSpecification>
<bpmn:dataOutputAssociation>
<bpmn:sourceRef>out_approved</bpmn:sourceRef>
<bpmn:targetRef>approvalFlagRef</bpmn:targetRef>
</bpmn:dataOutputAssociation>
</bpmn:userTask>
Completing this task with { "approved": true } writes approvalFlag: true to the instance. When a task declares dataOutputAssociations, only the associated targets are written. If the task also declares ioMapping outputs, the ioMapping outputs decide what is written and the dataOutputAssociations are ignored.
Chaining Between Tasks
When one task produces variables that the next task needs to read, declare matching dataOutput and dataInput:
[Fill Form] [Review Form]
dataOutput: name, email dataInput: name, email, amount
amount dataOutput: approved, notes
The reviewer sees the data the requester filled in because those variables are declared as dataInput of the reviewer's task. Stateway resolves this automatically.
If No Data Input Is Declared
The task receives no pre-filled variables. The form renders blank. This is the secure default — the process designer is required to be explicit about what to expose.
variable_visibility: "all"
When the session scope declares "variable_visibility": "all", all process variables are delivered without filtering. Use this only in development and test environments.
{
"scope": {
"definitions": [...],
"variable_visibility": "all"
}
}