Skip to Content
Flower 3.3.2 is released 🎉
DocsConceptsDecision Handling in Gateways
Image

BPMN Gateways & Decision Handling

Decide Process Paths with Expressions

BPMN Gateways & Decision Handling

A gateway lets a process choose between several paths. At an XOR gateway, Flower makes the choice automatically: every outgoing transition has a condition, and the process follows the first one that is true.

This guide explains how these decisions work, how to write expressions with the data of your Jira work items, and how to keep your process moving when data is missing.

How Gateway Decisions Work

When a process reaches an XOR gateway, the Flower engine evaluates the expressions that are stored on the outgoing transitions of the gateway. The process follows the first transition whose expression is true.

An expression is JavaScript code inside ${ }. Flower evaluates it with the data of your Jira work items, for example ${cur.fields.status.name == 'Rejected'}. Only the code inside the braces is evaluated.

Expressions on the outgoing transitions of an XOR gateway

ℹ️

If a transition loops back to an activity that is already completed, Flower reopens its work item.
For this, the workflow must offer a transition without a screen from the completed status back to a To Do or In Progress status. Otherwise, Flower cannot reopen the work item and adds an error comment to the process instance.

Write Expressions

An expression checks a value of a work item and returns true or false. Always write the whole expression inside ${ }. For example, ${pi.fields.summary} returns the summary of the process instance.

Choose the Right Work Item

Two work items are available in an expression:

NameWork itemTypical use
curThe current work item: the work item that was created by the last activity before the gatewayCheck the outcome of the last task, for example whether it was resolved, approved, or rejected
piThe process instance work itemCheck data from the start of the process that stays the same, such as fields of the process form

Both give you the work item as the Jira REST API returns it: fields contain the fields, properties contain the work item properties. You can also use external data through flowerVariables.

Example Expressions

#ExpressionWhat it checks
1${pi.fields.summary == 'New Request'}The summary of the process instance is New Request
2${cur.fields.status.name == 'Rejected'}The last work item has the status Rejected
3${cur.fields.resolution.name == 'Done'}The last work item was resolved as Done
4${cur.fields.priority.name == 'Low'}The last work item has the priority Low
5${cur.fields.assignee.accountId == '557058:fp60faa5-xxxx-4344-yyyy-4becd42z7914'}The last work item is assigned to this user
6${cur.fields.customfield_10113 >= 5000}A number field of the last work item is at least 5000
7${cur.fields.customfield_10115.value == 'yes'}A select field of the last work item has the value yes
8${pi.properties.flowerVariables.external.data.value >= 1000}External data stored on the process instance is at least 1000
9${pi.fields.comment.comments[0].author.accountId == '557058:fp60faa5-xxxx-4344-yyyy-4becd42z7914'}The first comment on the process instance was written by this user
10${pi.fields.customfield_10023.requestType.name == 'Technical support'}The request type of the process instance is Technical support (service space)

With such conditions, only the relevant transition is taken. This reduces manual input and automates decisions.

Work with Complex Fields

Not every field is as simple as the summary. Many fields, especially custom fields, are objects. Example 10 reads the request type of a service space work item.

Jira Custom Fields in General

Custom fields in Jira have a type, and most types are objects and not simple values. Many have a simple string in .value:

${pi.fields.customfield_1234.value == 'yes'}

Other fields have a more complex structure. To write the right path, look at the JSON of your work item.

Find the Structure of a Field

Custom field IDs differ between Jira instances. For example, the request type is stored in a custom field with a different ID in each instance.

To see the structure, open the work item in the Jira REST API. Open this URL in your browser while you are logged in to Jira:

https://your-domain.atlassian.net/rest/api/3/issue/FLOWER-123

Or use curl:

curl --request GET \ --url 'https://your-domain.atlassian.net/rest/api/3/issue/FLOWER-123' \ --user '[email protected]:<api_token>' \ --header 'Accept: application/json'

Example: The Request Type of a Service Space Work Item

Look for an entry like this in the response:

{ "customfield_10023": { "requestType": { "name": "Technical Task" } } }

This shows that customfield_10023 is the request type field in this instance.

Since the name of a request type can change, it is often safer to use its ID:

${pi.fields.customfield_10023.requestType.name == 'Technical support'} ${pi.fields.customfield_10023.requestType.id == '48'}

The same approach works for other complex fields. These paths go inside ${ }:

  • Priority: pi.fields.priority.name
  • Status: pi.fields.status.name
  • User fields: pi.fields.assignee.displayName
  • Any other custom field: pi.fields.customfield_xxxxxx

Example: JSON response of a work item in a service space

{ "expand": "renderedFields,names,schema,operations,editmeta,changelog,versionedRepresentations,customfield_10023.requestTypePractice", "id": "28406", "self": "https://your-jira-instance.atlassian.net/rest/api/latest/issue/28406", "key": "FLOWER-123", "fields": { "statuscategorychangedate": "2025-02-23T18:42:28.260+0100", "lastViewed": "2025-02-24T16:17:03.503+0100", "customfield_10100": [], "priority": { "self": "https://your-jira-instance.atlassian.net/rest/api/2/priority/5", "iconUrl": "https://your-jira-instance.atlassian.net/images/icons/priorities/trivial.svg", "name": "Trivial", "id": "5" }, "assignee": "user object", "status": { "self": "https://your-jira-instance.atlassian.net/rest/api/2/status/10000", "description": "This was auto-generated by JIRA Service Desk during workflow import", "iconUrl": "https://your-jira-instance.atlassian.net/images/icons/status_generic.gif", "name": "Waiting for support", "id": "10000", "statusCategory": { "self": "https://your-jira-instance.atlassian.net/rest/api/2/statuscategory/1", "id": 1, "key": "undefined", "colorName": "medium-gray", "name": "No Category" } }, "creator": "user object", "reporter": "user object", "customfield_11011": { "id": "13", "name": "Time to first response", "_links": { "self": "https://your-jira-instance.atlassian.net/rest/servicedeskapi/request/28406/sla/13" }, "completedCycles": [], "ongoingCycle": { "startTime": { "iso8601": "2025-02-23T19:42:27+0200", "jira": "2025-02-23T18:42:27.531+0100", "friendly": "Yesterday 7:42 PM", "epochMillis": 1740332547531 }, "breachTime": { "iso8601": "2025-02-24T18:00:00+0200", "jira": "2025-02-24T17:00:00.000+0100", "friendly": "Today 6:00 PM", "epochMillis": 1740412800000 }, "breached": false, "paused": false, "withinCalendarHours": true, "goalDuration": { "millis": 28800000, "friendly": "8h" }, "elapsedTime": { "millis": 27632453, "friendly": "7h 40m" }, "remainingTime": { "millis": 1167547, "friendly": "19m" } } }, "aggregateprogress": { "progress": 0, "total": 0 }, "progress": { "progress": 0, "total": 0 }, "votes": { "self": "https://your-jira-instance.atlassian.net/rest/api/2/issue/PC-3/votes", "votes": 0, "hasVoted": false }, "worklog": { "startAt": 0, "maxResults": 20, "total": 0, "worklogs": [] }, "issuetype": { "self": "https://your-jira-instance.atlassian.net/rest/api/2/issuetype/10126", "id": "10126", "description": "For customer support issues. Created by Jira Service Desk.", "iconUrl": "https://your-jira-instance.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/11228?size=medium", "name": "Support", "subtask": false, "avatarId": 11228, "hierarchyLevel": 0 }, "project": { "self": "https://your-jira-instance.atlassian.net/rest/api/2/project/10915", "id": "10915", "key": "PC", "name": "ProcessCreators", "projectTypeKey": "service_desk", "simplified": false }, "created": "2025-02-23T18:42:27.531+0100", "customfield_10023": { "_links": { "jiraRest": "https://your-jira-instance.atlassian.net/rest/api/2/issue/28406", "web": "https://your-jira-instance.atlassian.net/servicedesk/customer/portal/8/PC-3", "agent": "https://your-jira-instance.atlassian.net/browse/PC-3", "self": "https://your-jira-instance.atlassian.net/rest/servicedeskapi/request/28406" }, "requestType": { "_expands": [ "field" ], "id": "48", "_links": { "self": "https://your-jira-instance.atlassian.net/rest/servicedeskapi/servicedesk/8/requesttype/48?expand=requestTypePractice" }, "name": "Technical support", "description": "Need help installing, configuring, or troubleshooting? Select this to request assistance.", "helpText": "", "issueTypeId": "10126", "serviceDeskId": "8", "portalId": "8", "groupIds": [ "12" ], "icon": { "id": "11196", "_links": { "iconUrls": { "48x48": "https://your-jira-instance.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/11196?size=large", "24x24": "https://your-jira-instance.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/11196?size=small", "16x16": "https://your-jira-instance.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/11196?size=xsmall", "32x32": "https://your-jira-instance.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/11196?size=medium" } } } }, "currentStatus": { "status": "Waiting for support", "statusCategory": "UNDEFINED", "statusDate": { "iso8601": "2025-02-23T19:42:27+0200", "jira": "2025-02-23T18:42:27.531+0100", "friendly": "Yesterday 7:42 PM", "epochMillis": 1740332547531 } } }, "customfield_10024": { "id": "12", "name": "Time to resolution", "_links": { "self": "https://your-jira-instance.atlassian.net/rest/servicedeskapi/request/28406/sla/12" }, "completedCycles": [], "ongoingCycle": { "startTime": { "iso8601": "2025-02-23T19:42:27+0200", "jira": "2025-02-23T18:42:27.531+0100", "friendly": "Yesterday 7:42 PM", "epochMillis": 1740332547531 }, "breachTime": { "iso8601": "2025-02-25T18:00:00+0200", "jira": "2025-02-25T17:00:00.000+0100", "friendly": "25/Feb/25 6:00 PM", "epochMillis": 1740499200000 }, "breached": false, "paused": false, "withinCalendarHours": true, "goalDuration": { "millis": 57600000, "friendly": "16h" }, "elapsedTime": { "millis": 27632545, "friendly": "7h 40m" }, "remainingTime": { "millis": 29967455, "friendly": "8h 19m" } } }, "updated": "2025-02-23T18:42:33.013+0100", "description": "Your issue description", "summary": "Your issue summary" } }

User Objects

User fields such as assignee, creator, and reporter are objects:

Example: JSON of a user object

{ "self": "https://your-jira-instance.atlassian.net/rest/api/2/user?accountId=557058%3Afc60faa5-f40d-4344-ae2b-4becd42a7914", "accountId": "557058:fp60faa5-xxxx-4344-yyyy-4becd42z7914", "avatarUrls": { "48x48": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/557058:fp60faa5-xxxx-4344-yyyy-4becd42z7914/a3075f1a-94e7-4ea6-91bd-a0165342fcfd/48", "24x24": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/557058:fp60faa5-xxxx-4344-yyyy-4becd42z7914/a3075f1a-94e7-4ea6-91bd-a0165342fcfd/24", "16x16": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/557058:fp60faa5-xxxx-4344-yyyy-4becd42z7914/a3075f1a-94e7-4ea6-91bd-a0165342fcfd/16", "32x32": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/557058:fp60faa5-xxxx-4344-yyyy-4becd42z7914/a3075f1a-94e7-4ea6-91bd-a0165342fcfd/32" }, "displayName": "Power User", "active": true, "timeZone": "Europe/Athens", "accountType": "atlassian" }
ℹ️

The JSON structure in Jira Cloud and Jira Data Center is almost identical but may differ in some details.
Always check the data structure in your own Jira instance.

Integrating External Data into Gateway Decisions

Sometimes a decision depends on external data that is not stored in Jira. Flower lets you store such data as JSON on the process instance and use it in expressions.

Store External Data with the Jira REST API

Jira provides a REST endpoint for issue properties  that stores JSON on a work item. Store your data in the property flowerVariables of the process instance:

curl --request PUT \ --url 'https://your-domain.atlassian.net/rest/api/3/issue/FLOWER-2/properties/flowerVariables' \ --user '[email protected]:<api_token>' \ --header 'Accept: application/json' \ --header 'Content-Type: application/json' \ --data '{"some":"value"}'

The JSON can be as complex as you need, but it must not exceed 32,768 bytes. You can then use it in an expression on a transition:

${pi.properties.flowerVariables.some == 'value'}

With the data above, this expression is true.

Default Transitions: Ensuring the Process Keeps Moving

A default transition is the path that the process takes when no other condition is true, or when a condition fails with an error. This way, the process does not get stuck. Default transitions are especially useful when a decision depends on dynamic data, such as a custom field.

Default Transitions

In the Capital Investment Approval Process, an XOR gateway decides based on the custom field approval:

  • YES → CFO/Board Approval → Investment Execution
  • NO (default) → Rework Request → Resubmit Investment Request
  • REJECT → Investment Rejected → End Process

Why Use a Default Path?

If a condition fails, for example because a field value is missing or wrong, the process does not break. It follows the default path, and the requester can review, refine, and resubmit the investment request. Flower also writes a comment on the process instance that explains the failure, see Troubleshooting Expressions.

With a default path, your process stays robust, even if data in Jira is missing or inconsistent.

Troubleshooting Expressions

Flower can only check an expression at runtime, because only then the full execution context is available.

Keep these rules in mind:

RuleExample
Put the whole condition inside one ${ }${cur.fields.status.name == 'Rejected'} is a condition. In ${cur.fields.status.name} == 'Rejected', only the part in the braces is evaluated, and the result is text instead of true or false.
Expressions are case-sensitiveSummary is not summary
Compare types carefully'123' === 123 is false, because a string is not a number. The operator == converts types, so do not rely on it for strings and numbers.
Many fields store data in nested propertiesA custom field often needs .value or .name, for example ${cur.fields.customfield_10115.value == 'yes'}

If an expression cannot be resolved, Flower automatically writes a comment on the affected process instance:

Example: Expression validation failed

❌ Expression validation failed at: bpmn:ExclusiveGateway[Gateway_10td181] Expression: ${cur.fields.summary.value == 'a'} Error: Missing field 'value' at path 'cur.fields.summary.value' Context: pi: FLOW-106 cur: FLOW-107 model: FLOW-47 🔍 Tip: Use the Jira REST API to inspect your work item data: → /rest/api/latest/issue/FLOW-107 📘 Docs: https://flower-bpm.com/docs/concepts/gateway-decision-handling

In Jira Data Center, Flower also writes the full evaluation context to the server logs whenever an expression fails, so you can inspect the complete data structure.

Test your expressions with real work item data, especially for custom fields and advanced structures such as work item links or SLA objects. You can inspect a work item in the Jira REST API .

What’s Next?

Last updated on