Expressions
Flower evaluates expressions on the outgoing sequence flows of exclusive and inclusive gateways and in conditional events. The guide Decide with Gateways shows how to use them; this page is the complete reference.
Syntax
- An expression is a condition in a JavaScript-like syntax inside
${ }, for example${cur.fields.status.name == 'Rejected'}. Flower evaluates it with its own parser, which runs no code; what an expression can contain is listed in What an Expression Can Do. - Without the braces, Flower adds them:
cur.fields.status.name == 'Rejected'is the same condition. - Write the whole condition inside one pair of braces. In
${cur.fields.status.name} == 'Rejected', only the part in the braces is evaluated, and the result is a text such asDone == 'Rejected', which counts as true whatever the status is. - Names are case-sensitive:
summary, notSummary. Compare strings with strings and numbers with numbers. - The names of errors, escalations, messages, and signals in a model (for example
errorCode="Cancelled") are plain text, not expressions, unless they contain${ }. - A
${without its closing}is an error (the condition is false), and so is an expression longer than 2000 characters. - A script condition (a condition with a script language, from older models) is not executed: the condition is always false, the process instance gets the error comment script conditions are not executed, use an expression, and the model validator warns. Change it to an expression.
- Timers can be an expression too, for example
${pi.fields.customfield_10050}: it is evaluated first, and its value must be a duration, a date, or a number of days. An empty value means the timer is not set. See Define the Timer.
What an Expression Can Do
| Part | Allowed |
|---|---|
| Values | Texts ('text', "text", with the escapes of JavaScript such as \n, \u00fc, \x41), numbers (12, 1.5), true, false, null, undefined, lists (['High', 'Highest']) |
| Variables | pi, cur, par, model, and nothing else |
| Fields | pi.fields.summary, pi.fields.labels[0], cur.fields['customfield_10050'], a?.b; a negative list index counts from the end: pi.fields.labels[-1] is the last label |
| Operators | == != === !== < > <= >= && || ! ?? + - * / % ?: typeof, parentheses |
| Methods of a text | includes, startsWith, endsWith, indexOf, lastIndexOf, toLowerCase, toUpperCase, trim, trimStart, trimEnd, split, slice, substring, substr, charAt, at, concat, replace and replaceAll (with texts), padStart, padEnd, repeat, toString |
| Methods of a list | includes, indexOf, lastIndexOf, join, slice, concat, at, toString |
| Methods of a number | toFixed, toString |
| Functions | Number, String, Boolean, parseInt, parseFloat, isNaN, Array.isArray, Math.min, Math.max, Math.abs, Math.round, Math.floor, Math.ceil |
| Length | .length of a text or a list |
Not allowed: arrow functions and the list methods that need them (some, filter, map, …), =, new, template literals, Date, JSON, Object, the other functions of Math, regular expressions, any other function or method, and properties that an object only inherits (such as constructor). Such a condition is false, and the process instance gets an error comment that names what is not allowed (Expression could not be evaluated at …: Not allowed: Date.now(), once per process instance). The model validator reports it already when the model is published.
Differences to JavaScript: two objects or lists are equal only if they are the same one (pi.fields.priority == cur.fields.priority compares two separate objects and is false; compare .name or .id). A text an expression builds is limited to 100000 characters, a list to 10000 items.
Examples: ${pi.fields.labels.includes('approved')}, ${['High', 'Highest'].includes(cur.fields.priority.name)}, ${Number(pi.fields.customfield_10113) > 5000}, ${cur.fields.summary.toLowerCase().startsWith('urgent')}, ${pi.fields.customfield_10113 >= 5000 && cur.fields.priority.name != 'Low'}, ${pi.fields.labels.length > 0 ? pi.fields.labels[0] == 'vip' : false}.
Variables
| Variable | Contains |
|---|---|
pi | The process instance. Its fields are under pi.fields and also directly on pi (pi.summary is pi.fields.summary, in every run of the engine), its work item properties under pi.properties, plus pi.key and pi.id |
cur | The current work item: the work item whose change moved the process to this point, see What cur Is. Its fields are under cur.fields and also directly on cur (cur.summary), in every run |
par | The calling process instance in a sub-process (call activity). Outside a sub-process it is empty |
model | The model work item. Not set in the run that starts a process instance (up to its first wait); set in every later run |
Each work item is the JSON that the Jira REST API returns for it: fields and properties.
What cur Is
cur is the work item whose change made Flower run the process. In detail:
| Situation | cur |
|---|---|
| A work item of a task is resolved | That work item |
| After a parallel or inclusive join | The work item whose resolution completed the join, that is the branch that was resolved last |
| A conditional boundary event | The resolved work item of the activity the event is attached to |
| After a call activity | The sub-process instance (the work item of the call activity) |
| An escalation (status change of an open work item) | The escalated work item |
| An intermediate or start conditional event fires | The process instance, because its change was checked |
| After the timer job | The work item of the timer (boundary timer on a task) or the process instance (intermediate timer, timer start event, timer on a sub-process), loaded with all its fields. If several timers of the instance are due, the one the job found first: a work item with a due boundary timer before the process instance |
| Right after the start of a process instance | Empty: a condition on cur there is an unknown field (see below). Use pi |
| Inside an embedded sub-process | The same as in the main process |
Work items that the REST API creates with POST …/activities are created without the engine, so no expression is evaluated.
When an Expression Fails
| Problem | Result | Comment on the process instance |
|---|---|---|
A field that does not exist (a typo, a field that the work type does not have, cur right after the start) | The missing value counts as empty: ${pi.fields.typo} alone is false, but a comparison such as ${pi.fields.typo != 'x'} is true. Only a result that is falsy makes the condition false | An error comment Expression validation failed at … with the expression, the missing field, and the keys of pi, cur, and model. Once per process instance for each element and expression, not with every change: Flower remembers the last 50 reported ones in flower.reportedExpressions |
A field that exists but is empty, also a property of an empty field (${pi.fields.customfield_10115.value == 'yes'} with the field empty) | The condition is false | None |
| An expression that cannot be parsed, or that uses something not allowed | The condition is false | An error comment Expression could not be evaluated at … with the parser message, once per process instance |
| No outgoing flow of a gateway is true and there is no default flow | The process does not go on | An error comment The gateway … found no outgoing flow whose condition is true …, see BPMN Gateways |
par is used, but the calling process instance cannot be loaded | An error comment Could not load the calling process instance …, the expression object par is empty |
Example of the comment for a missing field:
❌ 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 issue data:
→ /rest/api/latest/issue/your-issue-key
📘 Docs: https://flower-bpm.com/docs/concepts/gateway-decision-handlingIn Jira Data Center, Flower also writes the evaluation to its log file, see Logging.
Complex Fields
Not every field is as simple as the summary. Many fields, especially custom fields, are objects. The request type of a service space work item is an example.
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.
External Data
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.