Administration Functions

Escalation Service

Discussed in its own page, see Escalation Service.

Switch User

This allows you to temporarily assume the identity of another user. You can use this for example to:

  • reproduce a problem a user is telling you they are having, e.g. diagnose a permissions problem

  • merge a pull request on behalf of another user (Bitbucket)

  • Update an issue on behalf of another user (if they are unavailable) (JIRA)

To use, enter the user ID of the user you wish to become, and press Run. To return to yourself, log out, then log in again.

When this script is used, it will generate a log entry in the audit log like so:

switch user audit log
You cannot su from a user having only administrative rights to a system administrator.

Change dashboard or filter ownership

Currently broken due to GRV-732.

This tool will change the owner for one or more dashboards from one existing user to another (JRA-19780 and others).

Dashboards comprise gadgets/portlets, which often refer to a filter. Just changing the dashboard ownership without any filters would show an error for the target user, so this tool will change any ownership of any filters that the dashboard contains using the following rules:

  • If the owner of the filter is the owner of the dashboard that you are modifying, the filter is re-owned as well

  • If the filter is owned by a third party, but the target user has permission to view it then no change is made

    • If the target user does not have permission to view it it’s shared globally Select the dashboards/filters to modify then hit the Preview button to see what will actually happen.


Bulk Fix Resolutions

This will change the resolution on all issues returned by a query you provide. Resolutions can often be wrong after an incorrect import, or after modifying the workflow, or if there have been problems in your workflow.

Another common problem is when doing a jira→jira migration, which might create a Resolution called UNRESOLVED. This script will allow you to modify them all without tampering with the database or reindexing.

To use it you’d create a filter that selects all issues with the bad resolution, then get the filter ID (from the URL), select the resolution you want to change it to or None, click Preview then Run.

After this has been done you should delete the bad resolution value to prevent it being used inadvertently.

Copy Project

This script allows you to create a new project based on another. It will copy:

  • Schemes

  • Role memberships

  • Custom field configurations

  • Optionally, versions and component

  • Optionally, all issues and their attachments in the source project

    • This is useful for creating template projects with issues with template documents

  • A JIRA Agile board related to the project

  • Filters and dashboards that begin with the project key

    • This is useful when your provisioning process requires giving new users a tailored dashboard and a set of filters related to their project (this is a niche requirement that you’re unlikely to need or want)

If you choose to copy issues, all links on issues in the source project will be copied, with one exception. Intra-project links on the project will be reproduced on the respective issues in the copied project. For instance, if you are copying project A to project B, and in A issue A-1 depends on A2, then B-1 will depend on B2. Again, useful for seeding a project with sample issues for onboarding new teams.

Copy Custom Field Values

For each issue returned by a query, this will copy values from one custom field to another. This is useful if you want to convert the type of a custom field.

If the two custom fields have different types, you may not be able to use this. The following conversions are handled:

  • Single to multi, eg single select to multi select, single user picker to multi user picker.

  • Multi to single, however only the first value will be retained.

  • Multi to text…​ the values are concatenated with a comma.

  • Short text to unlimited text.

  • Multi select to multi select.

If you are unsure try on a test instance first.

If the target field has already selected value(s), will be overwritten. Also for select lists (multi or single) if an option to be copied from source field is not configured to the available target field’s ones, then will be skipped.

Bulk Import Custom Field Values

This script is for importing multiple values into the options list for select, multiselect, radio, checkbox, or cascading select fields. The non-programming alternative is generally doing a dummy CSV import with each issue having a different value, then deleting the issues.

From the dropdown, choose the correct field configuration, which is shown below its custom field. Enter the options with one on each line.

For cascading selects, enter the sub-options indented under their parents (either with tabs or two spaces), for example:


This script will skip over already existing options, so it is safe to update options lists using this.

Split Custom Field Context

You may well never have had a need for this, if so move on!

This script applies to field config schemes for select, multiselect, radio, checkbox, or cascading select fields. Let’s say you have created a select list with various options and applied to two projects. After a time, one project wants to add/delete/deactivate one of the options, but the other project still requires it. Although you can create a new context and move one of the projects, this will lose the values for this field from its issues. Actually, they will display on the view screen, but when you come to edit the issue they will be lost.

This script will duplicate the options for a field config scheme, associate with one or more of the projects and issue types from the first screen, then migrate all the issues' field values to the new Option objects.

Start by selecting the field config scheme which you wish to split. Click next, and you will be shown a list of the current projects and issue types associated with. Choose one or more projects, then click Preview to see how many issues are affected, then Run.

At this time, splitting just an issue type is not supported.

Script Registry

The script registry lists all the ScriptRunner code you have used in your JIRA instance.

The code here is type-checked. You are advised to use the Script Registry on your staging instance after upgrading, to validate that all the APIs you rely on are still present.

Condition Tester

A utility for testing conditions that are used in various other of these scripts. See Simple scripted condition below for details.

Clear JIRA or Groovy Caches

Two built-in scripts for the price of one.

Clearing JIRA’s internal caches is useful if you have done something in the backend, such as modified one of the database tables. It fires a global clear cache event and will clear all caches that respond to that signal. Safe for production, the same thing happens when enabling/disabling plugins etc.

You can clear the groovy internal caches if you are developing in groovy. Classes should be automatically reloaded when they are modified, and this works fine for workflow functions etc. However it can fail to reload dependent classes. If that’s the case you can clear all compiled classes using this. Expect a delay for the screen to refresh, as many classes need to be recompiled.

Bulk Copy SLAs

This script allows you to copy the SLA configuration from one of your Service Desk projects to another one or more Service Desk projects.

This directly overwrites the SLA configuration in your destination project by the one in your source project. This means that if there is a mismatch between the issue types, you will find non relevant information in the destination SLA configuration. This isn’t going to break anything, but it will not be relevant to the project.

There is no point of having information about an issue type that you cannot have in your project type. To avoid this, we’ve added a preview option that will warn you if there are any issue type mismatches.

List scheduled jobs

This script lists the job runners and the job IDs that are scheduled in your instance. If the job has parameters then these are shown in the box below.

No parameters are required for this script, just click Run.

View server log files

Simply shows the last lines of the desired server log. This is useful for quickly checking the log files without having to connect to your server and find the log file, especially if you haven’t been given access to the server.

Workflow functions


JQL Query Condition

Enter a JQL query, if the current issue would be returned by the query, the condition is allowed. This is less flexible but possibly simpler to use than the simple scripted condition if you don’t have a programming background.

Before adding the query to your workflow you can test it by going to Admin → Built-In Scripts → Allows the transition if this query matches a JQL query. Enter the query and a sample issue key.

This is reasonably efficient even if the query would return millions of issues, as the query will be modified to something like:

{issue = 15130} AND {issuetype = "Task"}

Disclaimer: this wasn’t my idea originally.

Simple scripted condition

This script lets you use all the flexibility of a scripting language without having to get your hands too dirty.

Enter some code in the condition box. If this evaluates to true the condition passes and the transition is shown, if not it isn’t. You do not need to explicity return true or false…​ the value of the last statement sets the return value (when cast to a Boolean). However feel free to return true or false if you like.

There are a number of samples shown, if you click the link the code will be copied into the text box. You can then edit it as desired, or write something completely new.

There are plenty of other plugins that provide similar functionality, for instance checking values of fields. However this gives you greater flexibility, e.g. you can check something xor something_else, plus more advanced conditions such as all issues this one depends on are resolved.

Technical Stuff for Advanced Users

The conditions are mini groovy scripts. When they are modified, they are automatically compiled and cached.

The following variables are available:



Sample Usage



Table of custom field names (not IDs) to their values for this issue, used for quickly looking up CF values



User doing the transition, name is available as



Used for getting attachments



Current issue in this transition


Used for checking links



Could be used if the cfValues is not sufficient, or you need to check values on other issues



In validators, a copy of this issue before the user changed it in this transition. So for instance, to ensure the assignee was changed during a transition, in your validation condition you might put: issue.assignee != originalIssue.assignee



Base URL of JIRA, used for building URL links

Many of the built-in functions have a condtion option, as it’s not possible in JIRA to only activate a workflow post-function if some other condition is true. You can test conditions before setting them up for real on the Condition Tester admin script. If there is a syntax error it will be shown.

Safe navigation

Let’s say you want to check the resolution is Won’t Fix. You could write == "Won't Fix"

However, if the resolution is not set at all, ie is null, this code would produce an error:


Therefore it should be written with the safe-navigation operator, ie:

issue.resolution?.name == "Won't Fix"
Note the question mark.

Power Assertions

Power assertions are a very useful debugging tool. On the questionable line you are having problems with, simply begin it with assert.

As an example, let’s say I am trying to check the value for a custom field called MySelect, which has two values Yes and No. I want my workflow function to happen only if the value is set to Yes. As a naive first step, I try cfValues['MySelect'] == "Yes", this doesn’t work for an issue where it should, so I add the assert:

assert cfValues['MySelect'] == "Yes"

which gives the following failure:

image2013 3 25%2022%3A2%3A6

Note this is a bit counter-intuitive, as apparently both sides are "Yes", yet the == was false. Whenever you see something odd like this check the type of class:

assert cfValues['MySelect'].class == String.class
               |            |     |
               Yes          |     false
                            class com.atlassian.jira.issue.customfields.option.LazyLoadedOption

This tells us that value for this custom field key is actually a LazyLoadedOption, and we should call the getValue() method. It just so happens that the toString() method of Option returns the value, but you can’t compare them as strings.

Similarly, a multiselect is stored as a List of Options, so the correct code to use to check to see if a particular value is present is:


That is, use the spread-dot operator to call the getValue() method on every item, then we check if "AAA" is in that list.

In short, if you are having problems use the Condition Tester built-in script and add assert statements.

Remove the assert before using as a real condition - the assertion doesn’t return anything as such so your condition will always evaluate to false if you leave the assert in.

All sub-tasks must be resolved

Does what it says on the tin. The transition won’t be shown unless all sub-tasks are resolved. You can specify any resolution that the sub-tasks must be in or choose a particular one.


Simple scripted validator

The Simple Scripted Validator is a simple check for validating issues after they have been changed in a workflow transaction. You can validate that changes to multiple fields are coherent according to your business processes, likewise attachments, issue links, and parent/child relationships.

There are many uses for this function, several of them documented in the specific examples section.

Note that you can access the fields of the issue before it was changed in this transition using the originalIssue variable, see Technical Stuff for Advanced Users.


Send a custom email

This allows you to send email(s) after a transition, to support your business processes. The alternative to this is to define a new event type, new velocity templates, and map the event to the templates as documented here.

Using this post-function is comparitively simpler and more flexible.

When setting this up in the workflow you should preview it first, which will let you test the appearance of the email, and the recipient list etc.

Taking an example. Say you wanted to send a mail to the reporter and the CCB board when an issue with Critical or above priority is created. (A more useful example might be an email requesting approval for a sub-task for instance, or letting the QA team know an issue is ready for test).

Edit your workflow, and add a Script Post-function on the transition you want the email to be sent on, then choose Send Custom Email.

For Critical and Blocker priorities the condition might be in ['Critical', 'Blocker']

An email template example:

Dear ${issue.assignee?.displayName},

The ${} ${issue.key} with priority ${issue.priority?.name} has been assigned to you.

Description: $issue.description

<% if (mostRecentComment)
    out << "Last comment: " << mostRecentComment

Custom field value: <% out <<
    ) %>


Now fill in the email subject template. Clicking on the link below will pre-fill an example.

Set the email format. If you want to use an HTML template choose HTML.

Now you need to define the recipients

  • The field marked "To addresses" allows you to enter email addresses, for instance in this example.

  • "To issue fields" lets you specify fields on the issue. Both user and string fields are acceptable, for example assignee, reporter, watchers, user or user group custom fields, or custom fields holding valid email address

In the "Preview Issue Key" enter the key of an existing issue.

Press preview.

If there were no errors you should see something like the following:

email preview

Note the highlighted text which tells you that the condition evaluated to true, so an email would be fired in this case. You should now test with an issue that doesn’t have one of those priorities and make sure the condition evaluates to false, and for extra points, one where no priority is set at all.

Add the Send Custom Email post-function to the ToCCB_Board transition.

Finally, having tested that the recipients, the subject and email body look correct, you can add this as a post-function to your workflow.

Emails are added to your mail queue, which is flushed every one minute. If mail is disabled or no mail servers have been set up this won’t work (obviously)
Generally, where used as a post-function, this should be after the "Store the issue" function, so if you need access to links and attachments, you will get them.
If you are facing issues with characters appearing as question marks (???) then, in most cases, problems are due to a misconfiguration in the encoding, see JIRA Application internationalisation and encoding troubleshooting.
Comments in Emails

You will commonly want to reference the last comment in your email templates. The following variables are available for use in the templates:

Variable name



The comment made in this transition, if there was one. If not this will be null, hence you should wrap it an if block as shown in the example template.

In previous versions of the plugin this was set to the last comment on the issue, regardless if it was made in this transition or not. This was not the intended behaviour, but if you were relying on it then update your templates to use`mostRecentComment` instead.


The comment made in this transition, or if there wasn’t one, the most recent comment made prior to this transition.


The latest comment made prior to this transition. Using this, plus lastComment, can display a conversation thread.

Additional Configuration in Emails

You may notice the syntax for getting custom fields is a bit clunky, as the template engine does not allow you to use the import keyword. Rather than doing this, you can pass in a config map to the bindings for both the subject and body templates.

This is done in the Condition and Configuration section. Note that as this is used for the condition you must continue to return a boolean value to tell the system whether to send the email or not.

You can also pass in closures (functions) that you can call from the body template. A simple example of a Condition and Configuration section that defines config variables:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager

def customFieldManager = ComponentAccessor.getComponent(CustomFieldManager)
def storyPointsCf = customFieldManager.getCustomFieldObjectByName("TextFieldB")

// add value of story points to config map
config.storyPoints = issue.getCustomFieldValue(storyPointsCf)

// add a closure
config.capitalize = { String str ->

return true

The template:

Dear User,

Story Points: ${storyPoints}

Function: ${'hello sailor')}

Note the syntax for calling the closure from the template.

If the condition is going to return false, the config variables are irrelevant. So you can return false early…​

def condition = == "Open" && ...
if (! condition) return false

def expensiveConfigVars = [foo: "bar"]

config.putAll (expensiveConfigVars)
Dynamic Control over Recipients And From

Sometimes you want to dynamically alter the recipient or the sender, depending on issue attributes. The Condition and Configuration section has access to an empty mail object (a com.atlassian.mail.Email.

You can set attributes on it here, for example:

if ( == "Highest") {
else {

If you do it like this the To value of the configuration field becomes redundant, and you can leave it empty.

Another possible use of that field is to set email addresses stored in a custom field (that may not appear in the 'To issue fields' dropdown), for example:

import com.atlassian.jira.component.ComponentAccessor

def multiSelectAddressesCF = ComponentAccessor.getCustomFieldManager().getCustomFieldObjectByName("Multi Select Email Adresses")
def recipients = issue.getCustomFieldValue(multiSelectAddressesCF)?.join(",")


To support backwards compatibility the validation remains in place, so you will need to check the box at the bottom of the form which disables the validation of the configuration.:

email disable validation

If you want you can construct the mail completely dynamically, including the subject and the body, and ignore the rest of the form.

Rendering Wiki Markup Fields

If you are sending HTML mails you will want fields containing wiki markup to be converted to HTML. You can use the following code in your templates.

Only the standard Atlassian Wiki Renderer for JIRA is supported, not custom plugins supplying their own renderers.
Attachments in Emails

Unlike the standard JIRA email facility, this function can optionally include issue attachments in the mail. Useful for sending to offsite collaborators, who may not have JIRA accounts.

You have the choice of sending none, all, new (added this transition), or you can define a custom callback for complete control over what attachments to include. There are inline examples for this.

You may need to set specific JavaMail properties in JIRA’s startup settings so that different mail clients will get attachments with filenames properly encoded.

For example, if an attachment’s filename is particularly long and contains characters outside the ASCII range, then certain versions of Microsoft Outlook won’t render the filename correctly, and users may even have trouble downloading the attachment.

To address that, set the following properties in your JIRA instance’s startup scripts:


Fair warning: your mileage may vary. Whether this is needed for your instance will be dependent on your specific environment. Implementations of RFC 2231 vary widely across different mail clients.

Getting old and new custom field values

For events use:

def change = event?.getChangeLog()?.getRelated("ChildChangeItem").find {it.field == "Name of custom field"}
if (change) {
    out << 'Old: ' + change.oldstring + "\n"
    out << 'New: ' + change.newstring

For workflow functions use:

def change = transientVars.changeItems.find {it.field == "Name of custom field"}
if (change) {
    out << 'Old: ' + change.oldstring + "\n"
    out << 'New: ' + change.newstring
Control over From Address

By default we use the default from address for the SMTP server you have configured. This is because, in corporate environments, the mail relay will normally be configured to only allow a single sender. Encouraging people to set the From address caused confusion when the mail relay blocked it. Instead, you can set the Reply-to address, in the form, or using mail.setReplyTo(…​).

You can also set the way the sender appears in the mail client using mail.setFromName("Your Boss").

If you need to set the From address you can:

Including Query Results

You can include the results of a query in your email template - this will utilise the same velocity templates that JIRA uses.

In the template use:

${helper.issueTable("project = FOO and assignee = currentUser()", 10)}
The number 10 is an optional parameter that indicates the maximum number of issues to show. If there are more than this number, we do not currently provide links to the issue navigator to retrieve them.
There is an outstanding JIRA bug JRA-40986 which prevents images being inlined into the email, so recipients will need network connectivity to the JIRA instance in order for images to show.
Adding Generated Attachments

Sometimes you may wish to generate your own attachment and include it in the mail. Examples include:

  • an automatically generated invoice

  • a custom PDF or Office document

  • an Excel file with the output from a query

As you have access to the mail object in the Condition and Configuration section, you can add the attachment here, for example:

import javax.mail.internet.MimeBodyPart
import javax.mail.internet.MimeMultipart
import javax.mail.internet.MimeUtility

import javax.activation.DataHandler
import javax.activation.FileDataSource

def mp = new MimeMultipart("mixed")

def bodyPart = new MimeBodyPart()
bodyPart.setContent("some text content", "text/plain")

//def attFds = new FileDataSource("/path/to/file.xls")
//bodyPart.setDataHandler(new DataHandler(attFds))


A full explanation of everything you can do with JavaMail is out of scope, however above we demonstrate how to add a simple text attachment, and, commented out, how to attach a file from the file system.

Sending mail from a non-issue event

The built-in script supports only issue-related events, and also "watcher added/removed" events.

If you want to send mail from an event not related to an issue, such as when a new Version or user is created, you can use a Custom Listener configure to handle the appropriate event, and a script such as this:

import com.atlassian.crowd.event.user.UserCreatedEvent
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.mail.Email
import com.atlassian.mail.queue.SingleMailQueueItem

def event = event as UserCreatedEvent

def email = new Email("")
email.setSubject("User created")
email.setBody("User: ${event.user.displayName} created...")
// email.setMimeType("text/html") // for messages with an html body

SingleMailQueueItem item = new SingleMailQueueItem(email)

Fires an event when condition is true

This built-in script provides an alternative method for sending emails under certain conditions. Provide the name of the event to be fired, and the condition under which to fire it (see above). This method lets you use the "power" of velocity templates, but you will need to create a new event type, and associate it with your templates.

This function is useful if you only want to send mails to your Project Manager for high-priority issues or something similar.

Fast-track transition an issue

This post-function will automatically transition an issue if the provided condition holds true. Let’s take a contrived example again. Imagine that all new Critical priority issues should go to the CCB for approval when the issue is created.


To realise this we add this post-function on the Create Issue transition.

fast track to ccb
It’s not obvious how to get to the properties of the Create Issue transition. Click the first step, then the action will be available in the box at top right.
Place this as the last post-function, or at least after the Fire Event function. If you don’t do this, it won’t work.

So, if the issue has priority == Critical the issue will automatically have the "Requires CCB Approval" action applied on it.

When choosing a workflow action, note that you may see multiple actions of the same name. The key thing is the ID. It must be a valid transition from the target state of this action. You can get the ID by mousing over the links in the transition.

Note that if you need to provide additional parameters for doing a transition, for instance when Resolving an issue, you can enter code in the Additional Code field. The following sets the resolution and assigns to a user:

import com.atlassian.jira.component.ComponentAccessor

def constantsManager = ComponentAccessor.getConstantsManager()
def resolution = constantsManager.resolutions.findByName("Done")


We use IssueService to conduct the transition, which makes an effort to behave in the same way as the user interface.

If the field you are updating does not appear on the screen for the transition, or there is no screen, you should add the following line:


Using IssueService directly, you can only update fields on the issue if there is a screen for that transition, and you can only update those fields that appear on the screen. However, we allow you to update the issue even if there is no screen.

You can always add a comment using issueInputParameters.setComment('Some comment'). Do not also ignore the screen check if you do this, there is a bug that will cause the comment to be added twice.
Transition Options

These options allow you to skip validators, conditions and permissions on transition. For example this is useful when you want to force the transition to happen as a user that does not have the correct permissions, or you want to make use of the standard condition that hides the transition from users.

You can use this script multiple times on a transition, eg the Create Issue transition, to effect different default start statuses for an issue, depending on some value or other.

Clones the current issue to a new one. You can select which of the fields will be cloned (Fields to Clone field). If empty all system and custom fields are copied from the source issue.

There is also the possibility to select the user whom on behalf the issue will be cloned (As User field), it affects the creator and the reporter of the new cloned issue. If empty the currently logged in user will be used.

You can specify an issue type and/or target project, but if you leave them blank the clonee will have the same project and type as the cloner.

Specify also the link type and direction…​ this will normally be Clones, but doesn’t have to be.

If you want to override some of the field values rather than have them inherited from the source issue you can specify that in the Additional Code field. For example the following snippet will set the summary field, and a custom field called MyCustomFieldType to "my value":

issue.summary = 'Cloned issue'
def cf = customFieldManager.getCustomFieldObjects(issue).find { == 'MyCustomFieldType'}
issue.setCustomFieldValue(cf, "my value")

As of 2.1, outward and inward links are also copied. So if issue A depends on issue B, after cloning to issue C then issue C will also depend on issue B. And similarly if issue B was depended on by issue A.

You can override this behaviour to copy links selectively or disable altogether by providing a callback (a closure) called checkLink in the Additional Code field. The callback will be called for every link found and should return true or false. This is passed one parameter, the IssueLink object. For example, to disable the cloning of links altogether use this code:

checkLink = {link -> false};

To enable cloning of all links except links of the type Clones use:

checkLink = {link -> != "Clones"}
Control of cloning attachments

In a similar way, you can control which attachments are copied. To disable attachment copying completely:

checkAttachment = {attachment -> false}

To clone only the attachments added by the user jbloggs:

checkAttachment = {attachment -> attachment.authorKey == 'jbloggs'}

Due to indexing changes in JIRA 5.1, this post-function should be placed immediate after the function: Re-index an issue to keep indexes in sync with the database. If you don’t do this, the parent issue will not be indexed correctly.

After Create actions

There are some actions that can take place only after the creation of the issue (add watcher, create issue links etc), in this case you can apply additional actions after the creation of the new issue, using the doAfterCreate callback (closure).

For example, add watchers to the new issue:

import com.atlassian.jira.component.ComponentAccessor

def watcherManager = ComponentAccessor.getWatcherManager()
def userManager = ComponentAccessor.getUserManager()

doAfterCreate = {
    def userKeyToAddAsWatcher = "anuser"
    def watcher = userManager.getUserByKey(userKeyToAddAsWatcher)

    if (watcher) {
        watcherManager.startWatching(watcher, issue)
    else {
        log.warn("User with key: ${userKeyToAddAsWatcher} does not exist therefore would not be added as a watcher")

Another use of it could be, linking the new issue to another one. For example create a 'duplicates' issue link between the new issue and an issue with key JRA-24:

import com.atlassian.jira.component.ComponentAccessor

def issueLinkTypeManager = ComponentAccessor.getComponent(IssueLinkTypeManager)
def issueManager = ComponentAccessor.getIssueManager()

doAfterCreate = {
    def issueToLinkTo = issueManager.getIssueByCurrentKey("JRA-24")
    def duplicateLinkTypeName = "Duplicate"

    def duplicateLinkType = issueLinkTypeManager.getIssueLinkTypesByName(duplicateLinkTypeName)

    if (!duplicateLinkType) {
        log.warn ("Issue link type with name: ${duplicateLinkTypeName} does not exist")
    else {
        issueLinkManager.createIssueLink(,, duplicateLinkType[0].id, 1, currentUser)
The variable issue outside the doAfterCreate closure is not yet created and trying to access it will result to an error (the issue.getKey() will throw a NullPointerException). On the other hand the issue inside the doAfterCreate callback is created and exists (therefore the issue.getKey() will return the key of the new issue).

Create a sub-task

This is very similar to Clone an Issue above, except that it will create a sub-task. This function could be used multiple times to create several sub-tasks, for instance if you have an issue type that requires approval by 3 groups you might create a sub-task to represent each approval, and use the "Transition parent when all subtasks are resolved" to automatically transition the parent when each sub-task has been approved.

Let’s say we use this function twice to create two sub-tasks, so that our Finance and Marketing depts can give approval:


After the transition is made on the parent issue, we have the following sub-tasks:


Let’s say the information on the parent issue is not complete and Marketing reject their sub-task. Providing the two optional arguments shown are entered, the next time the parent moves through this transition then the issue will be Reopened, as opposed to another instance being created.


This is also useful for sub-tasks that represent whether an issue has passed testing in a given environment.

This post-function should be placed immediate after the function: Re-index an issue to keep indexes in sync with the database. If you don’t do this, the parent issue will not be indexed correctly.
To disable or selectively copy links, use the same method as described above in Clones an issue and links.

Transition parent when all subtasks are resolved

Put this post-function on the sub-task workflow on transitions where the workflow could be set, typically Resolve Issue. If all sibling sub-tasks are resolved this will transition the parent issue using the provided action, which should be valid for the current step. If it’s not valid, nothing will happen.

Using the example above, this is put on the Approve transition of the Approval issue type. When all child Approval records are approved, the parent is automatically transitioned to the Approved status. Because you will often want to resolve the parent, for convenience you can specify the resolution to be used for the parent record, if necessary.

I have not experienced this but users have reported problems if this is not after the Fire Event…​ in the list of post-functions.

Update blocking issues

Useful for alerting participants of other issues that a blocker is resolved, etc. Configure the link type (eg "blocks") and the comment to be added to blocked issues. Adding the comment will cause a mail to be sent (according to your notification scheme).

2012 05 01 2121

Put this function on your Resolve Issue transition. It should probably also be available as a listener, but it isn’t right now.

Set Issue Security

Useful for setting the security level on an issue according to some criteria about the issue or the reporter etc. Better to do it in an automated fashion than risk leaking information.

To set this up just specify the condition (multiple sample conditions are provided), and the desired issue security level. These are grouped by issue security level scheme…​ if you are applying this to multiple projects they should all use the same issue security level scheme. If the scheme or level doesn’t exist a message will be logged in your application log file, but other than that the user will not be alerted.

It didn’t set the desired security level?
  • Check your condition using in the condition tester admin script

  • Ensure your project is using the same scheme as you specified in the script options

  • Check the positioning of the function - it should be above the "store the issue" function, or for the create transition, above the "Creates the issue originally" function

image2013 3 12%2021%3A28%3A18

Add/Remove from/to active sprint

This function has two complementary parts - either adding an issue to an active sprint, or removing it from its current sprint.

For example, on the Start Progress transition you might apply this function so it’s automatically added to the current sprint. This is not the scrum way, where the entire sprint should be planned in advance, and if you finish early you should just twiddle your thumbs (not really true). However, sometimes you finish all the work in a sprint, and it’s a pain to then have to manually add everything you’ve worked on to the current sprint.

In the function configuration you provide a board name, the function will pick the first active sprint from that board. It’s possible that a future version will allow selection of the first unstarted sprint.

Changing the sprint requires the user to have the Schedule Issues permissions. There are two options here.

Select Act as other user: No if:

  • your users all have this permission, or

  • they don’t and you want this function to be ignored if they don’t

Select Act as other user: Yes if:

  • you want the action to happen as another user, who does have the Schedule Issue permission

If you choose Yes, you need to select a user who the sprint change will be done as. This will appear in the history as though the selected user had added the issue to the sprint.

You must define via code which user to do this as, which means you have plenty of options. Typically though you would choose a named user or the project lead, so those are the two examples provided:

add to sprint config

Executing Built-In Scripts Remotely

It’s possible to automate use of built-in scripts, i.e. calling them programatically. All scripts take a JSON payload. Currently this is url-encoded…​ which is likely to change in the future.

The payload contains the names and values of the parameters, plus the name of the built-in script we want to execute. The easiest way to get these is to execute the Preview function, and copy the scriptParams parameter.

Curl example

As an example, let’s use the script which bulk imports custom field options. We need to post data similar to the following, which will add the three options AAA, BBB and CCC.


Do not forget the scriptParams= part.

Put the above in a file, in this example called add-opt.json. Now post this to the correct endpoint using curl:

> curl -u admin:admin "http://<jira>/rest/scriptrunner/latest/canned/com.onresolve.scriptrunner.canned.jira.admin.BulkImportCustomFieldValues" -H "X-Atlassian-token: no-check" -H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" -H "Accept: application/json" --data "@add-opt.json"

{"output":"Added 2 new option(s) to scheme: <b>Default Configuration Scheme for SelectListA</b> for custom field: <b>SelectListA</b>. 1 option(s) already existed"}
In all the examples, enter appropriate administrator credentials.

If there is an error running the script, you will get a 500 status code, and a message.

HttpBuilder example

You can call these from code, for example from Groovy. The following example executes the Test Runner script:

testConfiguration = [
    FIELD_SCAN_PACKAGES: "com.onresolve.jira,com.onresolve.base,com.acme.scriptrunner.test",
    FIELD_TEST         : [

restClient.request(POST, JSON) {
    uri.path = "/$product/rest/scriptrunner/latest/canned/com.onresolve.scriptrunner.canned.common.admin.RunUnitTests"
    send URLENC, [scriptParams: new JsonBuilder(testConfiguration)]

Executing Script Console Scripts Remotely

You can also execute arbitrary code in the Script Console remotely. Due the url encoding this is a bit finickity. Assuming we have the following code in a file called script.groovy

log.debug ("hello")
log.debug ("sailor")

We can execute it using the following curl command:

> curl -u admin:admin -X POST "http://<jira>/jira/rest/scriptrunner/latest/user/exec/" -H "X-Atlassian-token: no-check" -H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" -H "Accept: application/json" --data-urlencode "scriptText@script.groovy"

To execute a file that already exists under a script root:

> curl -u admin:admin -X POST "http://<jira>/jira/rest/scriptrunner/latest/user/exec/" -H "X-Atlassian-token: no-check" -H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" -H "Accept: application/json" --data-urlencode "scriptFile=foo/bar.groovy"

Built-In Scripts Hacking Notes

You can add your own built-in scripts, or copy and hack the existing ones. Please review the Upgrading from Previous Versions section in addition to this.

Built-in scripts must implement the com.onresolve.scriptrunner.canned.CannedScript interface.

For hacking the distributed scripts copy it out to the file system, changing the file name, the class name, and at least get the value returned by the getName() method.

Any scripts in the package com.onresolve.scriptrunner.canned.jira which implements the CannedScript interface will be automatically recognised, and reloaded when they change. The recommended method of development is to have the script open in a connected instance of IntelliJ IDEA, make changes, then copy it out to one of the Script Roots, then reload the page.

For how-to questions please ask on Atlassian Answers where there is a very active community. Adaptavist staff are also likely to respond there.

Ask a question about ScriptRunner for JIRA, for for Bitbucket Server, or for Confluence.