Recipe: Validated Enum Dropdown
This recipe shows how to define an enumerated type inline in the BPMN and wire it to a user task so that only valid values are accepted on completion.
Scenario: A loan review task where the analyst must choose approved, rejected, or more_info_needed. The engine rejects any other value at the API level — no custom validation code required.
1. Define the Enum Type
Add the itemDefinition inside <bpmn:definitions>:
<bpmn:itemDefinition id="ReviewDecision" structureRef="stateway:inline">
<bpmn:extensionElements>
<stateway:schema kind="enum"><![CDATA[
{
"type": "string",
"enum": ["approved", "rejected", "more_info_needed"]
}
]]></stateway:schema>
</bpmn:extensionElements>
</bpmn:itemDefinition>
2. Declare a Typed Process Variable
Inside <bpmn:process>:
<bpmn:dataObject id="DecisionData" name="decision" itemSubjectRef="ReviewDecision" />
<bpmn:dataObjectReference id="DecisionDataRef" dataObjectRef="DecisionData" />
3. Wire the User Task
<bpmn:userTask id="review" name="Review Loan">
<bpmn:ioSpecification>
<bpmn:dataOutput id="out_decision" name="decision" itemSubjectRef="ReviewDecision" />
<bpmn:outputSet>
<bpmn:dataOutputRefs>out_decision</bpmn:dataOutputRefs>
</bpmn:outputSet>
</bpmn:ioSpecification>
<bpmn:dataOutputAssociation>
<bpmn:sourceRef>out_decision</bpmn:sourceRef>
<bpmn:targetRef>DecisionDataRef</bpmn:targetRef>
</bpmn:dataOutputAssociation>
</bpmn:userTask>
4. Complete BPMN
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions
xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:stateway="https://stateway.io/schema/bpmn/1.0"
targetNamespace="https://stateway.io/processes/loan-review">
<bpmn:itemDefinition id="ReviewDecision" structureRef="stateway:inline">
<bpmn:extensionElements>
<stateway:schema kind="enum"><![CDATA[
{ "type": "string", "enum": ["approved", "rejected", "more_info_needed"] }
]]></stateway:schema>
</bpmn:extensionElements>
</bpmn:itemDefinition>
<bpmn:process id="loan-review" isExecutable="true">
<bpmn:dataObject id="DecisionData" name="decision" itemSubjectRef="ReviewDecision" />
<bpmn:dataObjectReference id="DecisionDataRef" dataObjectRef="DecisionData" />
<bpmn:startEvent id="start">
<bpmn:outgoing>flow1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:userTask id="review" name="Review Loan">
<bpmn:incoming>flow1</bpmn:incoming>
<bpmn:outgoing>flow2</bpmn:outgoing>
<bpmn:ioSpecification>
<bpmn:dataOutput id="out_decision" name="decision" itemSubjectRef="ReviewDecision" />
<bpmn:outputSet>
<bpmn:dataOutputRefs>out_decision</bpmn:dataOutputRefs>
</bpmn:outputSet>
</bpmn:ioSpecification>
<bpmn:dataOutputAssociation>
<bpmn:sourceRef>out_decision</bpmn:sourceRef>
<bpmn:targetRef>DecisionDataRef</bpmn:targetRef>
</bpmn:dataOutputAssociation>
</bpmn:userTask>
<bpmn:endEvent id="end">
<bpmn:incoming>flow2</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="flow1" sourceRef="start" targetRef="review" />
<bpmn:sequenceFlow id="flow2" sourceRef="review" targetRef="end" />
</bpmn:process>
</bpmn:definitions>
5. Deploy
Upload the definition (save the XML above as loan-review.bpmn):
BPMN=$(base64 -w 0 loan-review.bpmn)
curl -X POST https://api.stateway.io/v1/definitions \
-H "X-API-Key: sw_live_your_key" \
-H "Content-Type: application/json" \
-d "{
\"key\": \"loan-review\",
\"name\": \"Loan Review\",
\"source_type\": \"bpmn-base64\",
\"source\": \"$BPMN\"
}"
6. Test
Start an instance:
curl -X POST https://api.stateway.io/v1/instances \
-H "X-API-Key: sw_live_your_key" \
-H "Content-Type: application/json" \
-d '{ "definition_key": "loan-review" }'
Get the task ID:
curl "https://api.stateway.io/v1/tasks?definition_key=loan-review" \
-H "X-API-Key: sw_live_your_key"
Complete with a valid value:
curl -X POST https://api.stateway.io/v1/tasks/task_01.../complete \
-H "X-API-Key: sw_live_your_key" \
-H "Content-Type: application/json" \
-d '{ "decision": "approved" }'
# → 200 OK, instance advances to the end event
Complete with an invalid value:
curl -X POST https://api.stateway.io/v1/tasks/task_01.../complete \
-H "X-API-Key: sw_live_your_key" \
-H "Content-Type: application/json" \
-d '{ "decision": "APPROVED" }'
Response 400:
{
"error": "ValidationError",
"errors": [
{
"path": "decision",
"value": "APPROVED",
"message": "must be equal to one of the allowed values: approved, rejected, more_info_needed"
}
]
}
The task stays in claimed state, waiting for a valid submission. Enum values are case-sensitive: "approved" works; "Approved" and "APPROVED" do not.
Frontend Gateway Integration
Because decision is a typed dataOutput with an inline enum schema, the Frontend Gateway automatically generates a <select> dropdown with options approved, rejected, and more_info_needed when rendering the task form — no form_schema configuration required.