Skip to main content

Data Stores

A DataStore lets one process read variables from instances of another process — directly in the BPMN diagram, without writing a service task or HTTP call.

The dependency is visible to anyone reading the diagram: a DataStoreReference symbol connects the two processes explicitly. There is nothing hidden in code.

Available in BPMN XML only

These constructs (itemDefinition, dataObject/dataObjectReference, dataStore/dataStoreReference) are parsed only from BPMN 2.0 XML definitions. JSON/YAML process definitions express a subset and cannot declare typed data objects, data stores, or inline types. See the Processes guide for JSON/YAML format limitations.

When to Use a DataStore

SituationDataStore?
A renewal process needs data from the original application
An invoice process needs the order details
A dashboard aggregates data across many instances
You need to write data to another process❌ (read-only)
Data comes from an external REST API❌ (use a serviceTask)

Two BPMN Elements

<bpmn:dataStore> — declared at <bpmn:definitions> level. Defines what data source this store represents.

<bpmn:dataStoreReference> — placed inside <bpmn:process>. Defines how to query the store from this process.

Declaring a DataStore

<bpmn:dataStore id="LoanStore" name="Loan Applications" itemSubjectRef="LoanSnapshotType">
<bpmn:extensionElements>
<stateway:storeConfig
backingType="processInstances"
definitionKey="loan-application"
identityVariable="loan_id" />
</bpmn:extensionElements>
</bpmn:dataStore>

storeConfig Attributes

AttributeRequiredDescription
backingTypeprocessInstances — look up variables from instances of another process definition
definitionKeyThe key of the process definition whose instances to query
identityVariableThe variable name in those instances that serves as the lookup key (e.g., loan_id)
definitionVersionlatest (default) or a specific version number

Placing a DataStoreReference

<bpmn:dataStoreReference id="LoanStoreRef" dataStoreRef="LoanStore">
<bpmn:extensionElements>
<stateway:storeAccess
mode="snapshot"
keyExpression="{{ variables.original_loan_id }}"
stateFilter="instance.status == 'completed' and variables.loan_status == 'disbursed'"
projection="amount, applicant, approved_at, interest_rate"
cardinality="single"
onNotFound="error" />
</bpmn:extensionElements>
</bpmn:dataStoreReference>

storeAccess Attributes

AttributeDefaultDescription
keyExpressionrequiredExpression that evaluates to the identityVariable value to look up
modesnapshotsnapshot: resolve once and copy values; live: re-resolve on every read
cardinalitysinglesingle: expect exactly 1 result; optional: 0 or 1; list: all results
onNotFounderrorerror: fire ValidationError; null: set target variable to null
stateFilter(none)Optional filter over instance.status and variables.* of the source instances
projection(all)Comma-separated list of variable names to include in the result

Key Expression

The keyExpression is evaluated against the current instance's variables at runtime:

{{ variables.original_loan_id }} — simple variable reference
{{ variables.order.id }} — nested property

The resulting string is matched against the identityVariable of instances in the target definition.

State Filter

stateFilter restricts which source instances are considered valid:

instance.status == 'completed'
instance.status == 'completed' and variables.loan_status == 'disbursed'
instance.status != 'error'

Supported operators: ==, !=, and, or, not.

Cardinality

ValueBehavior
singleExpects exactly 1 matching instance. 0 or 2+ triggers onNotFound behavior or an error
optional0 or 1 matching instances. 0 returns null or fires onNotFound; 1 resolves normally
listAll matching instances returned as an array

onNotFound

ValueBehavior
errorFires a ValidationError event. Catch it with a boundary event, or the instance moves to error state
nullSets the target variable to null. Execution continues normally

snapshot vs live

ModeWhen ResolvedCost
snapshotOnce, when the token first reaches the DataStoreReference elementOne DB query, low cost
liveEvery time the variable is accessed during executionRepeated queries, higher cost

Use snapshot for stable data (a completed original loan). Use live when the source instance can change while the current process runs and you need the latest value.

warning

live mode executes a database query on every access. A performance warning is logged when a live resolution takes more than 500ms. Configure the threshold with the DATASTORE_LIVE_MODE_THRESHOLD_MS environment variable.

Projection

projection limits which variables are copied from the source instance. Use it to avoid unintentionally copying large or sensitive fields:

projection="amount, applicant, approved_at"

Only amount, applicant, and approved_at are included in the resolved data. All other variables in the source instance are excluded.

Wiring a DataStore into a Task

After declaring the dataStoreReference, wire it to a process variable with a dataInputAssociation on the consuming task:

<!-- Process-level typed variable to hold the resolved data -->
<bpmn:dataObject id="OriginalLoanData" name="originalLoan" itemSubjectRef="LoanSnapshotType" />
<bpmn:dataObjectReference id="OriginalLoanRef" dataObjectRef="OriginalLoanData" />

<bpmn:serviceTask id="enrich" name="Enrich with Original Loan">
<bpmn:dataInputAssociation>
<bpmn:sourceRef>LoanStoreRef</bpmn:sourceRef>
<bpmn:targetRef>OriginalLoanRef</bpmn:targetRef>
</bpmn:dataInputAssociation>
</bpmn:serviceTask>

Before enrich executes, the engine resolves the DataStore, copies the projected variables into originalLoan, and validates them against LoanSnapshotType.

Tenant Isolation

DataStore queries are always scoped to the current tenant. A process belonging to tenant A can never read instances from tenant B, even if it somehow obtains their IDs. Isolation is enforced in the SQL query and cannot be overridden by the process definition.

Observability

Every DataStore resolution is written to the instance audit log. Use the history endpoint to inspect resolutions and measure latency:

curl https://api.stateway.io/v1/instances/inst_01.../history \
-H "X-API-Key: sw_live_your_key"
{
"data": [
{
"id": "evt_01...",
"action": "datastore.resolved",
"payload": {
"storeId": "LoanStore",
"dataStoreRef": "LoanStoreRef",
"definitionKey": "loan-application",
"mode": "snapshot",
"keyValue": "loan-2024-001",
"cardinalityFound": 1,
"durationMs": 12
},
"createdAt": "2026-06-12T10:30:00.000Z"
}
]
}

When resolution fails (onNotFound=error), the action is datastore.not_found with the same payload structure and cardinalityFound: 0.

Complete Example

See the Cross-Process Reference recipe for a full loan-renewal BPMN that uses a DataStore to look up the original loan, with boundary error handling for the not-found case.