(If you haven’t read the previous post in this series, I highly recommend starting with that.)

In order to make use of the Jira API documentation, we need to understand what classes, packages, and methods are.   That’s because the JIRA API is documented, but only in the most technical sense.  It is predicated on you having some knowledge of Java.    You can go to and get information about all of the internal libraries that Jira uses to function, but it’s not much good to you without that prior knowledge.

At the same time, knowing how to read and make use of the API documentation is a vital skill when it comes to working with ScriptRunner.   All of Jira’s functionality is available for you to use, but only if you can harness it through the power of classes.

What’s the Problem?

The problem I ran into when I was first starting to learn how to use ScriptRunner is that very little is written in a context that a beginner could make use of, and few examples are provided.  A lot of the Jira’s internal functions are interdependent, and someone who is new to both Groovy and Jira

I try to learn something new every day when it comes to Groovy, or at the very least to challenging my understanding of some of the tenets of the syntax that I think I already know.

This morning I was playing around with the assert statement in ScriptRunner, trying to understand the nuances of it.   I was getting frustrated because I wanted the assertion to fail, but then for the script to keep going.  I couldn’t figure out how to capture the error without having the script stop.

I tried a few things.   I know you can include a log statement to the end of an assertion, like so:

 assert (1 == 2) : "Assertion failed"  


 java.lang.AssertionError: Assertion failed. Expression: (1 == 2)
	at org.codehaus.groovy.runtime.InvokerHelper.assertFailed(
	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.assertFailed(

But even with the inclusion of the log statement, my script would grind to a halt when the assertion failed.    I also tried putting the assertion in a try/catch statement, but that didn’t work at all.

I then realized that I had no idea what the functional difference between an assertion and a try/catch statement actually was.  Moreover, I didn’t know when it was appropriate to use one or the

This is an extension of a previous post that I did, on returning all of the users in a Jira Cloud instance.

The request was for a script that returns all users in a Jira Cloud instance who have been inactive for over 30 days. Unfortunately, only part of this request is doable.  The Jira Cloud REST API does not currently provide any way of returning login or access timestamp information.  The most it will tell you is whether a user account is active or inactive. The only way to get access information is to export the Jira userbase through the Atlassian portal.

So the script below simply returns all Jira users in a Cloud instance who have an inactive account.   If/when the day comes that Atlassian adds login information to the API, I’ll update this post.

 import groovy.json.JsonSlurper

def sb = []
def page = 0

def lastPage = false

while (lastPage == false) {
  //run this loop until we detect the last page of results

  def x = 0
  //Every time the loop runs, set x to 0. We use x to count the number of users returned in this batch of results

  def getUsers = get("/rest/api/2/user/search?query=&maxResults=200&startAt=" + 

A long long time ago, I posted a complaint to this blog about how I had no idea what a Jira SearchContext was, and how it was very frustrating to try to figure it out.

Yesterday I realized that I had never really made any strides toward fixing my lack of knowledge around the search functionality of Jira.  I’m not talking JQL, I’m talking the actual Search Service waayyy down in the guts of Jira.

I set out wanting to update the permissions for a list of filters on Jira Server, based on the name of the server.  The list of filter names came from a migration we were doing from Jira Server to Jira Cloud. JCMA returned a list of the names of forty filters that were publicly accessible.  My task was to go in and update those permissions.  As I was going to have to repeat this process multiple times, it seemed an excellent candidate for a scripted solution.

As it turns out, it is extremely difficult to search for filters by name.   You can search for filters by their ID without issue, but searching by name is essentially not possible.

My next idea was to simply wipe


So you or your organization have decided to purchase, or at least trial, ScriptRunner. Now what?

If you’re going to learn how to use ScriptRunner, I would very strongly advise that you set it up in a test environment.  This allows you to learn to use the tool, and to not have to worry about making mistakes or deleting production data.

In order to use ScriptRunner, you’ll need to be an admin on the Jira system in question.   As well, I’ll do my best to explain the Groovy code that we’re using, but it would be of great benefit for you to read some primary learning materials on the subject.   Some of what I’ll say will assume a basic knowledge of object-oriented programming principles.   Please note that Groovy is case-sensitive, but is agnostic to whitespace.

All of the testing and learning we’re going to be doing is done in the ScriptRunner Script Console.   You can think of this as sort of a window into your Jira system, in which Groovy code can be run.  The Script Console is accessed by going to your Jira System Setings > Manage Apps > ScriptRunner > Script Console.  ScriptRunner has many

I am entirely self-taught when it comes to ScriptRunner and Groovy.   Everything I’ve learned has been through trial and error, and Googling different permutations of words until I find a solution.

A great deal of the information out there assumes that you already know how to work with ScriptRunner, Groovy, and Jira or Confluence.   I found this to be terrifically frustrating when I first started, as I did not have the requisite knowledge to make use of the information that I was finding.  I didn’t have the skills to put it into context, never mind making use of it in the specific use case to which I was trying to apply it.

For that reason, I’m going back to the beginning.  I’m starting an ongoing series of blog posts about how to get started with ScriptRunner for both Jira and Confluence.  You need to learn to walk before you can run, so for that reason I am calling this series the ScriptWalker series.

Not only will this hopefully be a resource for persons just starting out with ScriptRunner, but it will also force me to be sure that I can teach what I’m doing. In the end, that will make


Adaptavist has a tool called Microscope that Jira admins can use to look into the specifics of various aspects of the system, including workflows. If you’re looking to examine an instance, I recommend using Microscope rather than writing your own script.

However, it was requested that I look into workflows in a very specific way: the requester needed a tool that would take the name of a group and search all of the workflows for any references to that group.  In effect, they were searching for dependencies on that group within the workflows.

This script does not do that, but this is the basis upon which that script is built.  This script takes a workflow and returns the validator conditions for all of the transitions.  This could easily be adjusted to return the conditions for the triggers, etc.


 import com.atlassian.jira.workflow.WorkflowManager
import com.atlassian.jira.component.ComponentAccessor

def workflowManager = ComponentAccessor.getComponent(WorkflowManager)
def wf = workflowManager.getWorkflow("<workflow name>")
//wf is the workflow itself

def statuses = wf.getLinkedStatusObjects()
//statuses are the statuses available to that workflow

statuses.each {
  status ->
    //For each status associated with the workflow

    def relatedActions = wf.getLinkedStep(status).getActions()
  //relatedActions is the set of actions (transitions) associated with each status
  //log.warn("For the status " 

This was actually an interesting problem to solve.  Atlassian don’t seem to want anyone returning all of the users in an Jira instance through the API.   There’s supposedly a method for doing this, but it doesn’t work if you’re running the script through a Connect app like ScriptRunner.  This is another method that only works with an external script, as was the case with managing Cloud Confluence Space permissions.

Instead what we do is run an empty query through the user search. However, this presents its own set of challenges, as the body is only returned in a raw format. That is, instead of returning JSON, the HTTP request is returned as a byte stream. 

So after running the query, we turn it into text and then use the JSON Slurper to turn it into a JSON object with which we can work.

Despite the strangeness of the raw response, pagination, startAt, and maxResults still work, and are necessary to get all of the results.   Additionally, there is no flag in the HTTP response that pertains to the last page of results, such as “lastPage”. Therefor we must determine the final page of results ourselves.

The script starts by asserting

I spend a fair amount of time writing listeners with ScriptRunner, so that Jira will do various things for me based on certain criteria. For example, I write a lot of listeners that listen for when an issue is updated, and take action accordingly.

Until today, however, I had never thought how I might leverage the system to determine what kind of update had triggered the event.  I always just wrote the criteria in by hand, so that the listener would ignore anything I didn’t want eliciting a reaction.

The listener I was working on today got so complex, had so many nested IF statements and conditions, that it occurred to me to search for a better way.

As it turns out, the event object contains a lot of information, including which field’s change triggered the listener.  

In my own example, I was looking at the labels field.  I wanted the script to send a Slack message if the issue had been updated, but only if the labels had changed in a certain way.

The line of code required to check the type of update event doesn’t even require an import:

 def change = event?.getChangeLog()?.getRelated("ChildChangeItem")?.find {it.field == "labels"}

if (change) 

In my previous post I explored how to access the Confluence Cloud Space Permissions API endpoint. 

This Python script extends that, and gives a user a permission set in all Spaces in Confluence. This could be useful if you wanted to give one person Administrative rights on all Spaces in Confluence, for example.

Note that the user must first have READ/SPACE permission before any other permissions can be granted.

 from requests.models import Response
import requests
import json
headers = {
    'Authorization': 'Basic <Base-64 encoded username and password>',
    'Content-Type': 'application/json',
    'Accept': 'application/json',
userID = '<user ID (not name)>'
resp = requests.get(url, headers=headers)
data = json.loads(resp.text)
for lines in data["results"]:
  url="https://<url>"+lines["key"] + '/permission'
  dictionary = {"subject":{"type":"user","identifier": userID},"operation":{"key":"read","target":"space"},"_links":{}}
  data = data=json.dumps(dictionary)
      response =, headers=headers, data=data)
    print("Could not add permissions to Space " + lines["key"])