Script Listeners
Migrating from ScriptRunner for Jira Server/DC to Cloud? Check out our ScriptRunner Migration to Cloud section. |
Before you start
Visit ScriptRunner HQ to see example scripts. | |
Learn about event-based automating with Script Listeners. |
The condition script will be evaluated before your code is executed. In the case where a value other than true is returned then we get a false result, and the code will not execute. The condition is evaluated using the Jira Expression Framework.
You can refer to our Example Restrictions and Validators page for further details.
As a result of Atlassian's Transition to Forge Events and Missing Event Properties, it's important that you review and update any Script Listeners that depend on the missing properties. There is no workaround for retrieving these properties via the Atlassian REST API. Therefore, to ensure your scripts do not break after the transition, they need to be removed from any scripts that use them.
What are Script Listeners?
A listener is an automated procedure or function in ScriptRunner that waits (or listens) for a specific event to occur in Jira and then carries out an action if the event occurs. Listeners sit on your instance and wait for a webhook event to happen before executing the listener script. Webhooks are triggered after an action has occurred in Jira, such as when a space is created or a work item is updated.
Scripted Fields work items are updated after a script is executed, triggering an issue_updated webhook event in Jira. This event is sent to ScriptRunner, but does not cause the script to run again or trigger any associated script listeners configured for work item update events.
Script Listeners that update work items will trigger an issue_updated webhook event in Jira, which is then sent to ScriptRunner. If scripted fields are configured for the updated work items, they execute once, updating the work item and triggering another issue_updated webhook event without requiring any further processing.
How to use Script Listeners
You may want to use a Script Listener to:
- Populate a space with initial work items when it is created.
- Post a message to Slack when a work item is created.
- Store story points of sub-tasks against the parent work item.
Previously, it was important to ensure that your scripts did not inadvertently trigger events your script listener was listening for, as this risked causing an infinite loop. However, now self-triggering scripts can no longer execute after 10 runs. This restriction means that if a script creates an event that results in the same script running again, ScriptRunner will count the number of times this occurs and reject the 11th run. If this happens, you will see a log message informing you that Scriptrunner has cut off a self-triggering script loop.
Script Listeners and Post Functions
The crucial difference between Script Listeners and Post Functions is that the latter rely on a transition change, whereas the former depend on an event occurring, which can happen at any time.
Script Listeners give you more control over automated actions than you would get with a Post Function.
For example, whenever there is a Critical level priority work item in a specific space, you want a message to be sent to a Slack channel. If you use a post function to do this, the event will fire only after a transition, not when the work item is edited. Therefore, if the priority of the work item were edited to Critical, the post function would not catch it until after the work item had been transitioned. To achieve this use case, you would use a listener to catch a change in priority when it happens.
Create a Script Listener
Edit a Script Listener
Related content
- Take our ScriptRunner Tour.
- See our Example Script Listeners.
Script Listener behavior change on Forge events
Atlassian is retiring Connect, so ScriptRunner for Jira Cloud is moving to Forge. With that move, the script context variables in your Script Listeners, Perform Actions, and background syncing Scripted Fields such as issue, user, and changelog, are changing too. These variables now get their data from Atlassian's Forge events, which contain much less data than the events from the previous Connect platform.
Your scripts will continue to work without needing a rewrite. The variables keep their names and structure. When your script reads a property that is no longer included, ScriptRunner fetches it from Jira for you.
A line that only reads a value, such as issue.fields.priority, can now trigger an API call to Jira. If Jira rate-limits that call, ScriptRunner waits and retries once. If that second attempt also fails, the script stops with an error. This failure mode did not occur on Connect.
Script context changes
Script context variables such as issue, user, and changelog now derive their values from Atlassian Forge events instead of Connect. Because Forge events include less data, ScriptRunner may need to fetch missing values from Jira when your script accesses them.
In Atlassian's Forge platform, only a few properties are readily available, such as issue.key, issue.fields.summary, and a user's accountId. Others, such as issue.fields.priority, a custom field, or user.displayName, arrive when your script first reads them. Some properties are no longer available, and ScriptRunner cannot fetch them. You will need to change any scripts that use those properties; for details, refer to Atlassian's Transition to Forge Events and Missing Event Properties.
Why ScriptRunner fetches your data
When a property is missing from the Forge event, the only place to retrieve it is the Jira REST API. Previously, Atlassian included this data in the event, and ScriptRunner exposed it directly to your scripts, so you did not need additional API calls.
As we move to Forge, ScriptRunner fills in missing data only when your script first reads it. This on-demand fetching avoids unnecessary REST calls for properties your scripts never use and helps reduce load on Atlassian's systems.
Details of ScriptRunner fetches
| When your script reads | ScriptRunner fetches |
|---|---|
The whole issue or user variable, for example when you log it, turn it into JSON, send it in a request, or return it from the script. | All missing properties, including those your script never uses. |
A field of issue that is not included, such as description, priority, labels, or a custom field. | The work item. |
| The parent's summary, status, priority, or work type. | The parent work item. |
A user's displayName, avatarUrls, timeZone, or active. | The user, once per person, unless the user was already included with a work item that ScriptRunner fetched earlier. When Jira makes the change itself, shown as Anonymous user, there is no user to fetch, and these values remain null. |
fromString or toString on a changelog item, where it is missing. | The changelog entry. |
| Space, work type, or version details on those events. | The space, work type, or version. |
transitionInput.from_status in a workflow post-function. | The status. |
- Open Script Listeners.
- Click Run in the listener's History column.
- Open the Logs tab.
2026-09-28 12:24:52.445 INFO - Serializing object into 'interface java.util.Map' 2026-09-28 12:24:52.461 INFO - GET /rest/api/2/issue/BACKFILL-2 asObject Request Duration: 1360msissue.fields.priority.A user fetch appears as GET /rest/api/2/user?accountId= followed by the account ID. Any request that your script did not make itself is a fetch that ScriptRunner used to fill in the Script Context. Fetches that occur after your script finishes, for example, while ScriptRunner serializes a returned issue, do not appear in the log.
The fetched value is the work item as it exists when your script reads it. Previously, issue held the work item as it was when the event occurred. If someone changes the work item again in between, your script now sees the newer value.
What happens when a fetch fails
Atlassian limits how many API calls ScriptRunner can make to your site in a given period. Every call counts toward that limit: your scripts' own calls, these fetches, and ScriptRunner's internal checks, such as listener conditions. Bulk edits and imports fire one event per work item, so calls arrive in bursts, which is when your site is most likely to reach its limit.
GET request to /rest/api/2/issue/TEST-1 returned an error code: 429, retrying in 1s...| Jira answers | What your script gets |
|---|---|
| Not found (HTTP 404), or ScriptRunner may not see the data (HTTP 401 or 403) | null, with two warnings in the log. The run still succeeds. |
| Rate limited (HTTP 429) again after that retry, a server error, or no answer | The script stops with an error. |
2026-09-28 12:32:17.347 WARN - GET request to /rest/api/2/issue/BACKFILL-59 returned an error code: status: 404 - body: {errorMessages=[Issue does not exist or you do not have permission to see it.], errors={}} 2026-09-28 12:32:17.349 WARN - Enrichment of MappedIssueFieldsProperty via GET /rest/api/2/issue/{issueIdOrKey} found nothing (HTTP 404): the entity does not exist or is not visible to the app user, the field stays unset. 2026-09-28 12:32:17.350 INFO - Priority: nulCould not load the event data missing from the Forge payload (MappedIssueFieldsProperty via GET /rest/api/2/issue/{issueIdOrKey}): the Atlassian API responded with HTTP 429. The script was unable to continue due to an incomplete eventWhat changed for Workflow Perform Actions
Workflow Perform Actions switched to Forge events on September 29, 2026, on every site simultaneously. They no longer receive the Connect version of the event.
A Perform Actions issue variable now behaves like a listener’s. It holds the key and nine fields: summary, work type, status, space, creator, reporter, assignee, created, and updated. ScriptRunner fetches any other field when your script reads it, as described above.
Whose permissions apply. ScriptRunner fetches data as the user the Perform Action runs as. A Perform Action set to run as the Initiating User fetches with that user's permissions. If that user cannot see the data, the script gets null, with two warnings in the log.
Status names in transitionInput.transitionInput.from_status and transitionInput.to_status still hold status names. The new status comes with the event. ScriptRunner looks up the previous status when your script first reads it. That lookup does not need Jira administrator rights, so it works for any user the Perform Action runs as. If the lookup fails, the Perform Action stops with an error.
Before this fix, a Perform Action running as a non–Jira administrator could see a status ID, such as 10001, instead of the name. A condition such as transitionInput.to_status == 'Done' then evaluated to false, so the Perform Action skipped its work.
Conditions. A Perform Action's condition can read fields that require a fetch. If that fetch fails, the Perform Action now stops with an error. Previously, the condition evaluated as false and the Perform Action did nothing without reporting an error.
Clone Issue. The built-in Clone Issue Perform Action reads the work item once and copies it as it exists at that moment. The copy includes changes that earlier post-functions on the same transition made. Its additional code sees the same values.
When a Perform Action fails. A Perform Action runs after the transition, so a failed Perform Action does not undo the transition. Only the Perform Action's own work is missing, and ScriptRunner does not run it again.
Listeners don't run for restricted comments
If a listener uses the Comment created or Comment updated event, it might not run when the comment is restricted to a group or space role. It still runs as expected for comments that everyone can see.
Why this happens
Jira only sends a comment event to an app if the app can see that comment. ScriptRunner follows the same comment visibility rules as any other Jira user. If it isn't in the group or space role the comment is restricted to, Jira sends no event, and ScriptRunner never knows the comment was added or edited. Nothing appears in Execution History, because the listener was never triggered.
Atlassian sometimes tightens how webhooks are sent for restricted content to meet data privacy standards. So a listener that previously ran for restricted comments can stop running even though you haven't changed anything. Also, moving ScriptRunner from Connect to Forge can change the app user's internal ID so role memberships may no longer apply.
How to fix it
- Find the group or project role your restricted comments use, such as Administrators or Developers.
- Go to Project settings > People in each affected space, and add the ScriptRunner app user to that project role.
- If the restriction uses a group, add the ScriptRunner app user to that group.
- Add a restricted comment to test, and check that the run shows up in the listener's Execution History.
How to write scripts that handle failed fetches
Fetch the data yourself when the script must decide what happens
def response = get("/rest/api/2/issue/${issue.key}") .queryString("fields", "priority") .asObject(Map) if (response.status != 200) { logger.warn("Could not read ${issue.key} (HTTP ${response.status}), skipping this run") return } logger.info("Priority: ${response.body.fields.priority?.name}")issue, ScriptRunner fetches the work item a second time.Do not retry by reading the property again
null:def priority try { priority = issue.fields.priority?.name } catch (Exception e) { priority = issue.fields.priority?.name // null: ScriptRunner does not fetch again in this run If a decision depends on fromString or toString, fetch the changelog yourself and check the response status.
Keep the number of calls down
Use the properties that are readily available when they are enough.
Read the properties you need, instead of logging or serialising the whole
issueoruser.Limit each listener to the spaces and events it needs.
Check Execution History after bulk changes
After a bulk edit or import, open Execution History from the ScriptRunner menu and check for failed Script Listener and Perform Action runs. The longest interval is 72 hours, so review the history within three days.