Workflow Restriction and Validator Expression Examples
You can create custom workflow restrictions and workflow validators using Jira expressions. This page provides a library of example expressions you can use and adapt to suit your workflow. The examples are grouped by topic to help you find what you need.
What are Jira expressions?
Jira expressions is a scripting language developed by Atlassian for evaluating logic in Jira Cloud. They use a JavaScript-like syntax and give you access to a set of context variables, such as issue, user, and project, that represent the current state of Jira at the point the expression is evaluated.
Jira Expressions are not JavaScript. Only the properties and methods documented in the Jira Expressions type reference are available. Expressions must always return a Boolean (true or false). A result of true allows the transition to proceed; false blocks it.
Limitations using Jira expressions
Some restrictions apply to evaluating expressions using the Jira expression framework. An expression can execute up to 10 expensive operations. Expensive operations are those that load additional data, such as entity properties, comments, or custom fields.
Atlassian provides information on Jira expression types and lists the properties and functions which trigger expensive operations so that users can determine the operation complexity of expressions being evaluated.
To check the complexity of an expression before deploying it, you can use the Evaluate Jira expression REST API operation with the meta.complexity expand parameter to see the number of expensive operations triggered.
Workflow restrictions vs validators: Which to use?
| Restrictions | Validators | |
|---|---|---|
| When it runs | Before the transition screen is shown | After the user submits the transition screen |
The effect when false | The transition is hidden | The transition is visible but blocked with an error message |
| Custom error message | No | Yes |
| Can access transition-time data | No (screen not shown yet) | Yes (e.g. comment or attachment being added right now) |
Most expressions on this page work for both. They check the existing state of the work item, such as fields, links, sub-tasks, dates, and user permissions, which is available in both contexts. A small subset are validator-only and labelled as such.
Choosing between them:
- Use a restriction to silently hide a transition until conditions are met (for example, hide Close until all sub-tasks are done).
- Use a validator when you want the transition visible but need to guide the user with a clear error message (for example, "Please add a comment before closing").
Examples
Users and permissions
These expressions control who can perform a transition based on the current user's identity, space role, or group membership. Use them to ensure only the right people can move work items through specific workflow stages.
- Restrict to a space role
- Restrict to a user group
- Check if the current user is in a named list of users
- Check if the current user is within a group from a specified list
- Specify that the current user must be in a defined list of users
- Require specific users in a space to create work items of a specified type
- The current logged-in user has added at least one comment
- User selected in a custom user picker field
- Group selected in a custom group picker field
Restrict to a space role
Checks whether the current user belongs to the specified space role:
javascriptuser.getProjectRoles(project).some(p => p.name == "Developers")
Replace Developers with the space role name. Roles are space-scoped; user groups are global. See Atlassian's documentation for managing project roles.
Restrict to a user group
Checks whether the current user belongs to the specified group:
javascriptuser.groups.includes('jira-administrators')
Replace jira-administrators with the group name. Roles are space-scoped; user groups are global. See Atlassian's documentation to view, create, or delete a group.
Check if the current user is in a named list of users
Checks whether the current user is in a specified list, by display name:
javascript['User A', 'User B', 'User C'].includes(user.displayName)
Check if the current user is within a group from a specified list
Checks whether the current user belongs to at least one group from a specified list:
javascript['Group A', 'Group B', 'Group C'].some(g => user.groups.includes(g))
Specify that the current user must be in a defined list of users
Checks whether the current user is in a specified list, by account ID:
You can also use user.displayName, however, account IDs are unique and stable; they never change even if a user updates their display name.
javascript['accountIdHere', 'accountIdHere'].includes(user.accountId)
Require specific users in a space to create work items of a specified type
Checks whether the current user is permitted to create the specified work item type. In this example, only members of the Developers role can create Bug work items; all other users can create any work item type:
javascriptissue.issueType.name == 'Bug' ? user.getProjectRoles(project).map(p => p.name).includes("Developers") : true
The current logged-in user has added at least one comment
Checks that the current logged-in user has added at least one comment to the work item:
javascriptissue.comments.some(c => c.author.accountId == user.accountId)
User selected in a custom user picker field
Checks whether a specific user is selected in a user picker custom field. Single user picker:
javascript// by display name issue.customfield_10213?.displayName == 'A User' // or using accountId issue.customfield_10213?.accountId == '5cf7c174eba28b4ea84a7cb5'
Multi-user picker:
javascriptissue.customfield_10219 ? issue.customfield_10219.some(user => user.displayName == 'A User') : false
Group selected in a custom group picker field
Checks whether a specific group is selected in a group picker custom field. Single user picker:
javascriptissue.customfield_10077?.name == 'jira-admins'
Multi-group picker:
javascriptissue.customfield_10078 ? issue.customfield_10078.some(c => c.name == 'jira-admins') : false
Fields and values
These expressions validate the content of work item fields before or during a transition. Use them to enforce that required fields are populated, that field values meet specific criteria, or that text matches an expected pattern.
- Field is required
- At least one of two fields must have a value
- Multi-select or checkbox must contain a specific value
- Multi-select or checkbox must equal an exact set of values
- Multi-select or checkbox must contain one specific value
- Work item description must contain more than X characters
- Field changed at any point in the work item's lifetime
- Field changed in the most recent update
- Regular expression match on description or a custom text field
- Require at least one fix version
- Require at least one component
How to find a custom field ID
See Atlassian's documentation for information on how to find a custom field ID.
Field is required
Checks that a text, single-select, multi-select, radio button, checkbox, or cascading select field is not empty:
javascriptissue.customfield_10040 != null
Cascading select, parent value only:
javascriptissue.customfield_10266?.value != null
Cascading select, both parent and child:
javascript// You cannot set the child without the parent issue.customfield_10266?.child?.value != null
Rich text custom field, required and non-empty:
javascriptlet plainTextValue = value => typeof value == 'Map' ? new RichText(value).plainText : value; plainTextValue(issue.customfield_10080 != null) && plainTextValue(issue.customfield_10080) != ''
At least one of two fields must have a value
Checks that at least one of two fields has a value:
Replace customfield_12345 and customfield_67890 with the IDs of the fields in your space. You can extend this pattern with additional || or && clauses for more fields.
javascriptissue.customfield_12345 != null || issue.customfield_67890 != null
Multi-select or checkbox must contain a specific value
Checks that a multi-select or checkbox field contains a specific value:
javascriptissue.customfield_10263 ? issue.customfield_10263.some(option => option.value == "End Users") : false
Multi-select or checkbox must equal an exact set of values
Checks that a multi-select or checkbox field contains exactly a specified set of values:
javascriptissue.customfield_10214 ? issue.customfield_10214.map(option => option.value) == ['Yes', 'No'] : false
Multi-select or checkbox must contain one specific value
Checks that a multi-select or checkbox field contains at least one specific value:
javascriptissue.customfield_10214 ? issue.customfield_10214.some(option => option.value == "A") : false
Work item description must contain more than X characters
Checks that the work item description is longer than a minimum number of characters:
Change 30 to the minimum number of characters required.
javascriptissue.description.plainText.length > 30
Field changed at any point in the work item's lifetime
Checks whether a specified field has been changed at any point in the work item's lifetime:
Replace Field Name with the name of the field to check.
javascriptissue.changelogs.some(c => c.items.some(i => i.field == 'Field Name'))
Field changed in the most recent update
Checks whether a specified field was changed in the most recent update:
javascript// changelogs[0] is the most recent entry issue.changelogs[0].items.some(i => i.field == 'Field Name')
Regular expression match on description or a custom text field
These examples check that the description or custom text field value matches a regular expression.
The Regular Expression condition is only available on strings, including text fields and option values.
Regular expression matches description:
javascriptissue.description.plainText.match("SRJ-\\d+") != null
Regular expression matches custom text field value:
javascriptissue.customfield_10206 ? issue.customfield_10206.match("SRJ-\\d+") != null : false
Require at least one fix version
Checks that the work item has at least one fix version:
javascriptissue.fixVersions.length > 0
Require at least one component
Checks that the work item has at least one component:
javascriptissue.components.length > 0
Comments
These expressions validate the comments on a work item. Use them to require a comment during a transition, enforce a minimum comment length, or, in Jira Service Management, control whether comments are public or internal.
- Require a comment during transition (validator only)
- All comments must meet a minimum length
- All existing comments must be public (Jira Service Management)
- Comment added during transition must be internal (Jira Service Management) (validator only)
Require a comment during transition (validator only)
Checks that a comment is being added during the current transition:
A comment being added during a transition has not yet been persisted, so its id is null. Because the transition screen hasn't been shown yet when a restriction runs, this expression only works as a Validator rule.
javascriptissue.comments.some(comment => comment.id == null)
All comments must meet a minimum length
Checks that all comments on the work item are longer than a minimum number of characters:
Change 10 to the minimum number of characters required.
javascriptissue.comments.every(comment => comment.body.plainText.length > 10)
All existing comments must be public (Jira Service Management)
Checks that all existing comments on the work item are public:
javascriptissue.comments.every(c => !c.properties["sd.public.comment"].internal)
Comment added during transition must be internal (Jira Service Management) (validator only)
Checks that any comment being added during the current transition is marked as internal:
The internal property is a string ('true'/'false') for comments being added during a transition (i.e. id == null), not a boolean. Because this checks for a comment being added right now, it only works as a Validator rule.
javascriptissue.comments.every(c => c.id != null || c.properties["sd.public.comment"].internal == 'true')
Attachments
These expressions validate the attachments on a work item. Use them to ensure that required files are present before a transition can proceed, or to enforce that a minimum number of attachments are added during the transition itself.
- Require at least one PDF attachment
- Require a minimum number of PDF attachments
- Require a minimum number of PDFs added during the current transition (validator only)
- Require a minimum number of attachments added during the current transition (validator only)
Require at least one PDF attachment
Checks that the work item has at least one PDF attachment:
javascriptissue.attachments.some(attachment => attachment.mimeType == 'application/pdf')
Require a minimum number of PDF attachments
Checks that the work item has more than a specified number of PDF attachments:
Change 2 to the minimum number of PDFs required.
javascriptissue.attachments.filter(attachment => attachment.mimeType == 'application/pdf').length > 2
Require a minimum number of PDFs added during the current transition (validator only)
Checks that a specified number of PDF attachments are being added during the current transition:
Change > 1 to the minimum number of PDFs required. The transition screen must include the Attachments field so users can submit attachments during the transition. Because this checks for attachments being added right now, it only works as a Validator rule.
javascriptissue.attachments.filter( attachment => attachment.id == null && attachment.mimeType == 'application/pdf' ).length > 1
Require a minimum number of attachments added during the current transition (validator only)
Checks that a specified number of attachments are being added during the current transition:
Change 3 to the minimum number of attachments required. The transition screen must include the Attachments field so users can submit attachments during the transition. Because this checks for attachments being added right now, it only works as a Validator rule.
javascriptissue.attachments.filter(attachment => attachment.id == null).length == 3
Dates and times
These expressions compare date and time values on a work item against the current date. Use them to enforce time-based rules, such as requiring a work item to be resolved within a certain period or ensuring a due date is set within an acceptable range.
- Work item was created more than 7 days ago
- Work item was updated within the last 2 hours
- Work item was resolved within the last 30 days
- Work item is due within the next 3 months
- Date custom field is 30 or more days in the future
- Date-time custom field is less than 6 hours in the future
The following system fields return a Date object: created, updated, resolutionDate.
Work item was created more than 7 days ago
Checks that the work item was created more than 7 days ago:
javascriptissue.created < new Date().minusDays(7)
Work item was updated within the last 2 hours
Checks that the work item was updated within the last 2 hours:
javascriptissue.updated > new Date().minusHours(2)
Work item was resolved within the last 30 days
Checks that the work item was resolved within the last 30 days:
resolutionDate custom fields can be null. Always check for null before comparing.
javascriptissue.resolutionDate ? issue.resolutionDate >= new Date().minusDays(30) : false
Work item is due within the next 3 months
Checks that the work item is due within the next 3 months:
dueDate custom fields can be null. Always check for null before comparing. The dueDate field returns a CalendarDate object (date only, no time).
javascriptissue.dueDate ? issue.dueDate <= new CalendarDate().plusMonths(3) : false
Date custom field is 30 or more days in the future
Checks that a date custom field is 30 or more days in the future:
Date custom fields store a string value. Convert it to a CalendarDate using new CalendarDate(issue?.customfield_10200).
javascriptissue.customfield_10200 ? new CalendarDate(issue.customfield_10200) >= new CalendarDate().plusDays(30) : false
Date-time custom field is less than 6 hours in the future
Checks that a date-time custom field value is less than 6 hours in the future:
DateTime custom fields store a string value. Convert it to a Date using new Date(issue?.customfield_10217).
javascriptissue.customfield_10217 ? new Date(issue.customfield_10217) <= new Date().plusHours(6) : false
Sub-tasks
These expressions validate the state of a work item's sub-tasks. Use them to enforce that sub-tasks exist, are assigned, or have reached a required status before the parent work item can transition.
- Require sub-tasks
- Sub-tasks must be done
- Sub-tasks must be in progress
- Sub-tasks must have an assignee
- Sub-tasks of a specific type must be at a specific status
Require sub-tasks
Checks that the work item has at least one sub-task:
Change > 0 to the minimum number of sub-tasks required.
javascriptissue.subtasks.length > 0
Sub-tasks must be done
Checks that all sub-tasks are in the Done status:
Change Done to the required status name.
javascriptissue.subtasks.every(subtask => subtask.status.name == 'Done')
Sub-tasks must be in progress
Checks that all sub-tasks are In Progress:
This expression cannot be used on the Create transition. Use it on the Start Progress transition.
javascriptissue.subtasks.every(subtask => subtask.status.name == 'In Progress')
Sub-tasks must have an assignee
Checks that all sub-tasks have an assignee:
javascriptissue.subtasks.every(subtask => subtask.assignee != null)
Sub-tasks of a specific type must be at a specific status
Checks that all sub-tasks of a specific type are at a specific status:
javascriptissue.subtasks .filter(s => s.issueType.name == 'Scope Change') .every(d => d.status.name == 'Done')
Linked work items
These expressions validate the linked work items associated with a work item. Use them to enforce that links exist, or that linked work items have reached a required status or resolution before the current work item can transition.
- Require at least one linked work item
- All linked work items must be in a specific status
- All linked work items of a specific link type must be in a specific status
- All linked work items must have a resolution
- All linked work items of a specific link type must have a resolution
Require at least one linked work item
Checks that the work item has at least one linked work item:
You can edit this expression to require other fields. For example, replace links with fixVersions or components.
javascriptissue.links.length > 0
All linked work items must be in a specific status
Checks that all linked work items are in a specific status:
javascriptissue.links.every(l => l.linkedIssue.status.name == 'Done')
All linked work items of a specific link type must be in a specific status
Checks that all linked work items of a specific link type are in a specific status:
direction can be 'inward' or 'outward', corresponding to the inward/outward labels of the link type (e.g. "is blocked by" / "blocks").
javascriptissue.links .filter(l => l.direction == 'inward') .filter(l => l.type.inward == 'is blocked by') .every(l => l.linkedIssue.status.name == 'Done')
All linked work items must have a resolution
Checks that all linked work items have a resolution:
javascriptissue.links.every(l => l.linkedIssue.resolution != null)
All linked work items of a specific link type must have a resolution
Checks that all linked work items of a specific link type have a resolution:
javascriptissue.links .filter(l => l.type.inward == 'is blocked by') .every(l => l.linkedIssue.resolution != null)
Work item type and sprint
These expressions check the work type or sprint state of a work item. Use them to restrict transitions based on what kind of work item it is, or to ensure it is part of an active sprint before work can begin.
Work item must be in the current active sprint
Checks that the work item is in the current active sprint:
javascriptissue.sprint?.state == 'active'
All stories in an epic must be done
Checks that all stories in the epic are in the Done status:
javascriptissue.isEpic && issue.stories.every(story => story.status.name == 'Done')
Verify work type
Checks whether the work type is Bug or Task:
javascript["Bug", "Task"].includes(issue.issueType.name)
Status and history
This expression inspects the changelog of a work item. Use it to check whether a work item has previously passed through a specific status.
Work item has been in a specific status previously
Checks that the work item has previously been in a specific status:
javascriptissue.changelogs.some(c => c.items.some(i => i.toString == 'In Progress') )
Common mistakes
These are the most frequent errors when writing Jira expressions for restrictions and validators. Each example shows what goes wrong and how to fix it.
Nested if statements
Jira expressions do not support nested if statements. For example, the following expression will fail:
javascriptif (issue.summary.length > 10 ) { if(issue.assignee) { // ... } }
You can instead combine this into a single expression, as shown in the example below:
javascriptif (issue.summary.length > 10 && issue.assignee) { // ... }
Returning a list instead of a boolean
Restrictions and validators must return a boolean. The following expression returns a list of objects and will fail:
javascript// ❌ Will NOT work — returns a list, not a boolean issue.comments.map(c => c.body) // Use .some() or .every() to produce a boolean instead // ✅ Correct issue.comments.every(comment => comment.body.plainText.length > 10)
Missing issue. prefix on a field reference
A common typo when checking multiple fields; the second field reference is missing the issue. prefix:
javascript// ❌ Will NOT work — customfield_67890 is undefined issue.customfield_12345 != null || customfield_67890 != null // ✅ Correct issue.customfield_12345 != null || issue.customfield_67890 != null