Process Variables and Context Access Guide
Overviewβ
Understanding how variables work in BankLingo Process Engine is crucial for building robust workflows. This guide explains:
- How to access the
contextobject in scripts - How
resultVariableworks vs default storage - How variables flow between tasks
- Best practices for variable management
The Context Objectβ
What is context?β
context is a JavaScript object that contains ALL process variables at any point in the workflow execution:
// context contains:
{
// Initial variables (from runtimeContext)
customerId: "12345",
loanAmount: 50000,
// Results from previous tasks
customerData: { ... },
creditScore: { ... },
// Results from user tasks
approved: true,
approverComments: "..."
}
Where Context Comes Fromβ
The engine builds context from the process's stored variables:
Key Point: When a script executes, the engine automatically passes all process variables as the context object. You don't need to explicitly request or configure thisβit happens automatically.
Accessing Context in Different Task Typesβ
1. ScriptTaskβ
In ScriptTask, access context directly:
<bpmn:scriptTask id="Task_Calculate" name="Calculate Monthly Payment">
<bpmn:script><![CDATA[
// Access initial variables
const principal = context.loanAmount; // From runtimeContext
const rate = context.interestRate / 100;
const term = context.termMonths;
// Access previous task results
const creditScore = context.creditScoreData.score; // From previous task
// Perform calculation
const monthlyRate = rate / 12;
const numPayments = term;
const payment = principal *
(monthlyRate * Math.pow(1 + monthlyRate, numPayments)) /
(Math.pow(1 + monthlyRate, numPayments) - 1);
// Return result (will be stored in resultVariable or lastScriptResult)
return {
monthlyPayment: payment,
totalPayment: payment * numPayments,
totalInterest: (payment * numPayments) - principal
};
]]></bpmn:script>
</bpmn:scriptTask>
Available in context:
- All initial variables from
runtimeContext - All
resultVariablevalues from previous tasks - All
lastScriptResultfrom previous tasks (if no resultVariable) - All signal data from UserTasks
2. ServiceTaskβ
In ServiceTask, the entire context is automatically passed to the command:
<bpmn:serviceTask id="Task_FetchCustomer" name="Fetch Customer"
custom:resultVariable="customerData">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="ConnectorKey" value="FetchCustomerCommand" />
<custom:property name="ResultVariable" value="customerData" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:serviceTask>
Behind the scenes:
The engine automatically generates the command invocation: doCmd('FetchCustomerCommand', context) where context contains all process variables.
Your command receives:
The command handler will receive properties mapped from the context. For example, if context contains customerId and loanAmount, those values are automatically mapped to your command's properties.
3. Gateway (Conditional)β
In Gateway conditions, access context for decision-making:
<bpmn:exclusiveGateway id="Gateway_CheckScore" name="Check Credit Score">
<bpmn:incoming>Flow_1</bpmn:incoming>
<bpmn:outgoing>Flow_Approve</bpmn:outgoing>
<bpmn:outgoing>Flow_Reject</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:sequenceFlow id="Flow_Approve" name="Good Score"
sourceRef="Gateway_CheckScore"
targetRef="Task_Approve">
<bpmn:conditionExpression>
context.creditScoreData.score >= 700 && context.loanAmount <= 50000
</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<bpmn:sequenceFlow id="Flow_Reject" name="Poor Score"
sourceRef="Gateway_CheckScore"
targetRef="Task_Reject">
<bpmn:conditionExpression>
context.creditScoreData.score < 700 || context.loanAmount > 50000
</bpmn:conditionExpression>
</bpmn:sequenceFlow>
4. UserTaskβ
UserTask receives context data for display and adds signal data to context:
<bpmn:userTask id="Task_ReviewApplication" name="Review Loan Application">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Form" value="LoanReviewForm" />
<!-- Form can access context variables for display -->
</custom:properties>
</bpmn:extensionElements>
</bpmn:userTask>
When user completes the task:
{
"cmd": "SignalProcessInstanceCommand",
"data": {
"instanceId": "123",
"taskId": "Task_ReviewApplication",
"signalData": {
"approved": true,
"comments": "Customer has excellent payment history",
"approvedBy": "JohnDoe"
}
}
}
Signal data is merged into context:
// Before signal
context = {
customerId: "12345",
creditScoreData: { ... }
}
// After signal
context = {
customerId: "12345",
creditScoreData: { ... },
approved: true, // From signal data
comments: "...", // From signal data
approvedBy: "JohnDoe" // From signal data
}
ResultVariable vs Default Storageβ
The Problem with Default Storageβ
Without resultVariable, results get overwritten:
<!-- Task 1 -->
<bpmn:scriptTask id="Task1" name="Calculate Score">
<bpmn:script>
doCmd('calculateScore', { customerId: context.customerId })
</bpmn:script>
</bpmn:scriptTask>
<!-- Task 2 -->
<bpmn:scriptTask id="Task2" name="Check History">
<bpmn:script>
doCmd('checkHistory', { customerId: context.customerId })
</bpmn:script>
</bpmn:scriptTask>
What happens:
// After Task1
context.lastScriptResult = { score: 750, rating: "Good" }
// After Task2 - Task1 result is LOST!
context.lastScriptResult = { transactions: [...], balance: 15000 }
// β Can't access score anymore!
context.lastScriptResult.score // undefined (overwritten!)
The Solution: Use ResultVariableβ
With resultVariable, results are preserved:
<!-- Task 1 -->
<bpmn:scriptTask id="Task1" name="Calculate Score"
custom:resultVariable="creditScore">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="ResultVariable" value="creditScore" />
</custom:properties>
</bpmn:extensionElements>
<bpmn:script>
doCmd('calculateScore', { customerId: context.customerId })
</bpmn:script>
</bpmn:scriptTask>
<!-- Task 2 -->
<bpmn:scriptTask id="Task2" name="Check History"
custom:resultVariable="customerHistory">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="ResultVariable" value="customerHistory" />
</custom:properties>
</bpmn:extensionElements>
<bpmn:script>
doCmd('checkHistory', { customerId: context.customerId })
</bpmn:script>
</bpmn:scriptTask>
<!-- Task 3 -->
<bpmn:scriptTask id="Task3" name="Make Decision">
<bpmn:script>
// β
Both results available!
const score = context.creditScore.score; // 750
const balance = context.customerHistory.balance; // 15000
doCmd('makeDecision', { score, balance });
</bpmn:script>
</bpmn:scriptTask>
What happens:
// After Task1
context.creditScore = { score: 750, rating: "Good" }
// After Task2 - Task1 result still available!
context.creditScore = { score: 750, rating: "Good" }
context.customerHistory = { transactions: [...], balance: 15000 }
// Task 3 can access both
context.creditScore.score // 750 β
context.customerHistory.balance // 15000 β
How ResultVariable Worksβ
Implementation Detailsβ
When a script or command returns a value, the engine stores it using the specified ResultVariable name. If no ResultVariable is specified, the result is stored in a default variable called lastScriptResult.
Storage Logic:
- If
ResultVariableis defined β Use that name - If
ResultVariableis empty β Use"lastScriptResult"as fallback - Result is saved to the process state and available to subsequent tasks
Storage Behaviorβ
| Scenario | Storage Location | Access Pattern |
|---|---|---|
| No ResultVariable | context.lastScriptResult | Gets overwritten each time |
| With ResultVariable | context.{ResultVariable} | Preserved permanently |
| ServiceTask default | context.lastServiceResult | Gets overwritten each time |
| ServiceTask with ResultVariable | context.{ResultVariable} | Preserved permanently |
Complete Variable Flow Exampleβ
Process Definitionβ
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:custom="http://banklingo.com/schema/bpmn"
xmlns:custom="http://banklingo.app/schema/bpmn">
<bpmn:process id="LoanProcess" name="Loan Application Process">
<!-- Start -->
<bpmn:startEvent id="Start" />
<!-- Task 1: Fetch Customer (ServiceTask) -->
<bpmn:serviceTask id="Task_FetchCustomer" name="Fetch Customer"
custom:resultVariable="customer">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="ConnectorKey" value="FetchCustomerCommand" />
<custom:property name="ResultVariable" value="customer" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:serviceTask>
<!-- Task 2: Calculate Score (ScriptTask) -->
<bpmn:scriptTask id="Task_CalcScore" name="Calculate Credit Score"
custom:resultVariable="creditScore">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="ResultVariable" value="creditScore" />
</custom:properties>
</bpmn:extensionElements>
<bpmn:script><![CDATA[
// Access customer from Task 1
const customerId = context.customer.id;
const accountBalance = context.customer.accountBalance;
// Access initial variables
const loanAmount = context.loanAmount;
// Call command
return doCmd('calculateCreditScore', {
customerId,
accountBalance,
loanAmount
});
]]></bpmn:script>
</bpmn:scriptTask>
<!-- Task 3: Review Application (UserTask) -->
<bpmn:userTask id="Task_Review" name="Review Application">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Form" value="ReviewForm" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:userTask>
<!-- Task 4: Make Decision (ScriptTask) -->
<bpmn:scriptTask id="Task_Decision" name="Make Final Decision"
custom:resultVariable="finalDecision">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="ResultVariable" value="finalDecision" />
</custom:properties>
</bpmn:extensionElements>
<bpmn:script><![CDATA[
// Access ALL previous results
const score = context.creditScore.score;
const customerName = context.customer.name;
const approved = context.approved; // From UserTask signal
const comments = context.reviewComments; // From UserTask signal
// Make decision
return {
approved: approved && score >= 700,
reason: approved ? comments : "Rejected by reviewer",
approvedAmount: context.loanAmount,
interestRate: score >= 750 ? 7.5 : 9.0
};
]]></bpmn:script>
</bpmn:scriptTask>
<!-- End -->
<bpmn:endEvent id="End" />
</bpmn:process>
</bpmn:definitions>
Execution Timeline with Context Evolutionβ
Step 1: Start Processβ
Request:
{
"cmd": "StartProcessInstanceCommand",
"data": {
"processDefinitionId": 15,
"runtimeContext": {
"customerId": "CUST123",
"loanAmount": 50000,
"term": 24
}
}
}
Context:
{
customerId: "CUST123",
loanAmount: 50000,
term: 24
}
Step 2: After Task_FetchCustomerβ
Task executed: ServiceTask with resultVariable="customer"
Context now:
{
// Initial variables (still available)
customerId: "CUST123",
loanAmount: 50000,
term: 24,
// Task 1 result (added)
customer: {
id: "CUST123",
name: "John Doe",
accountNumber: "ACC001",
accountBalance: 15000,
employmentStatus: "employed"
}
}
Step 3: After Task_CalcScoreβ
Task executed: ScriptTask with resultVariable="creditScore"
Script accessed:
context.customer.idβ "CUST123"context.customer.accountBalanceβ 15000context.loanAmountβ 50000
Context now:
{
customerId: "CUST123",
loanAmount: 50000,
term: 24,
customer: { ... }, // Still available
// Task 2 result (added)
creditScore: {
score: 750,
rating: "Good",
factors: [
{ name: "Payment History", value: "Excellent" },
{ name: "Account Balance", value: "Good" }
]
}
}
Step 4: UserTask Signalβ
Task waiting: UserTask Task_Review
Signal request:
{
"cmd": "SignalProcessInstanceCommand",
"data": {
"instanceId": "123",
"taskId": "Task_Review",
"signalData": {
"approved": true,
"reviewComments": "Customer has excellent history",
"reviewedBy": "Jane Smith",
"reviewDate": "2025-12-18"
}
}
}
Context now:
{
customerId: "CUST123",
loanAmount: 50000,
term: 24,
customer: { ... },
creditScore: { ... },
// UserTask signal data (merged)
approved: true,
reviewComments: "Customer has excellent history",
reviewedBy: "Jane Smith",
reviewDate: "2025-12-18"
}
Step 5: After Task_Decisionβ
Task executed: ScriptTask with resultVariable="finalDecision"
Script accessed:
context.creditScore.scoreβ 750context.customer.nameβ "John Doe"context.approvedβ truecontext.reviewCommentsβ "Customer has excellent history"
Final Context:
{
// Initial variables
customerId: "CUST123",
loanAmount: 50000,
term: 24,
// Task 1 result
customer: {
id: "CUST123",
name: "John Doe",
accountNumber: "ACC001",
accountBalance: 15000,
employmentStatus: "employed"
},
// Task 2 result
creditScore: {
score: 750,
rating: "Good",
factors: [...]
},
// UserTask signal data
approved: true,
reviewComments: "Customer has excellent history",
reviewedBy: "Jane Smith",
reviewDate: "2025-12-18",
// Task 4 result (final)
finalDecision: {
approved: true,
reason: "Customer has excellent history",
approvedAmount: 50000,
interestRate: 7.5
}
}
Common Patternsβ
Pattern 1: Sequential Data Enrichmentβ
// Task 1: Fetch basic data
context.customer = { id: "123", name: "John" }
// Task 2: Enrich with credit data
context.creditData = { score: 750, history: [...] }
// Task 3: Enrich with account data
context.accountData = { balance: 15000, transactions: [...] }
// Task 4: Use all enriched data
const decision = {
customer: context.customer,
credit: context.creditData,
account: context.accountData
};
Pattern 2: Conditional Processingβ
// Gateway condition
if (context.creditScore.score >= 700) {
// Route to auto-approval
} else {
// Route to manual review
}
Pattern 3: Accumulating Resultsβ
// Initialize array in first task
context.validationResults = [];
// Each validation task appends
context.validationResults.push({
validator: "CreditCheck",
passed: true,
message: "Credit score acceptable"
});
// Final task checks all validations
const allPassed = context.validationResults.every(r => r.passed);
Best Practicesβ
1. Always Use ResultVariableβ
β Bad:
<bpmn:scriptTask id="Task1">
<bpmn:script>
return { score: 750 };
</bpmn:script>
</bpmn:scriptTask>
β Good:
<bpmn:scriptTask id="Task1" custom:resultVariable="creditScore">
<bpmn:extensionElements>
<custom:property name="ResultVariable" value="creditScore" />
</bpmn:extensionElements>
<bpmn:script>
return { score: 750 };
</bpmn:script>
</bpmn:scriptTask>
2. Use Descriptive Variable Namesβ
β Bad:
context.data1
context.result2
context.temp
β Good:
context.customerData
context.creditScore
context.riskAssessment
3. Check for Undefined Valuesβ
β Bad:
const score = context.creditScore.score; // May crash if undefined
β Good:
const score = context.creditScore?.score ?? 0; // Safe access with default
4. Document Your Variablesβ
Add comments in your process definition:
<bpmn:documentation>
Process Variables:
- customerId: String - Customer unique identifier
- loanAmount: Number - Requested loan amount
- customer: Object - Customer details from Task_FetchCustomer
- creditScore: Object - Credit score data from Task_CalcScore
- score: Number (0-850)
- rating: String ("Excellent", "Good", "Fair", "Poor")
- approved: Boolean - From UserTask Task_Review
</bpmn:documentation>
5. Keep Context Cleanβ
Don't pollute context with unnecessary data:
β Bad:
context.tempData = [...]; // Never used again
context.debugInfo = {...}; // Not needed
β Good:
// Only store what future tasks need
return {
score: calculatedScore,
rating: getRating(calculatedScore)
};
Troubleshootingβ
Issue: "context is undefined"β
Cause: Trying to access context before process starts
Solution: Ensure initial variables are passed in runtimeContext
Issue: "context.variableName is undefined"β
Cause: Variable not set by previous task
Solution: Check that previous task has resultVariable set
<!-- Fix: Add resultVariable -->
<custom:property name="ResultVariable" value="variableName" />
Issue: Result from Task 1 not available in Task 3β
Cause: Task 2 overwrote lastScriptResult
Solution: Use unique resultVariable names
<!-- Task 1 -->
<custom:property name="ResultVariable" value="task1Result" />
<!-- Task 2 -->
<custom:property name="ResultVariable" value="task2Result" />
<!-- Task 3 can access both -->
<bpmn:script>
const result1 = context.task1Result;
const result2 = context.task2Result;
</bpmn:script>
Issue: Signal data not in contextβ
Cause: Signal data keys don't match expected names
Solution: Verify signal data property names
// Sending signal
{
"signalData": {
"approved": true // Must match context.approved
}
}
Summaryβ
Key Conceptsβ
context= All process variables at current pointresultVariable= Named storage for task results- Default storage =
lastScriptResult/lastServiceResult(gets overwritten) - Variables persist = All context data flows through entire process
- Signal data merges = UserTask completions add to context
Memory Aidβ
context = {
...initialVariables,
...task1Result (if resultVariable set),
...task2Result (if resultVariable set),
...userTaskSignalData,
...taskNResult
}
Next Stepsβ
- Learn about ScriptTask for inline code
- Explore ServiceTask for external services
- See Complete Examples for full workflows
- Read Execution Modes for supervised execution