Can I run the built in scripts externally

Rich Scire August 8, 2019

I'd like to automate the creation of new project when they involve mirroring existing one. To do this, I'd like to verify all the project info, then call the Scriptrunner built-in copy project script. Is there anyway to invoke the script externally, through REST or some other means?

2 answers

2 accepted

3 votes
Answer accepted
Peter-Dave Sheehan
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
August 8, 2019

There are a few options.

You can call the built-in script directly via rest from within your jira instance as if you were calling it remotely:


But you can also implement the canned script within your own scripts. Either in your own custom Rest Endpoint or from a workflow post function.

You just have to import the com.onresolve.scriptrunner.canned.jira.admin.CopyProject class and supply all the inputs.

Here is a custom REST API I've actually implemented and have allowed some non-admins users to run (the script runs as admin). 

It's called by just providing a json like this:

{"sourceKey":"ABC", targetKey: "NEW", "targetName":"New Project", "projectLead":"leadUsername"}


The REST API script:

import com.atlassian.jira.component.ComponentAccessor

import com.onresolve.scriptrunner.canned.jira.admin.CopyProject
import groovy.json.JsonSlurper
import groovy.transform.BaseScript
import groovy.transform.Field


@BaseScript CustomEndpointDelegate delegate
@Field projectManager = ComponentAccessor.projectManager
@Field userManager = ComponentAccessor.userManager
@Field baseUrl = ComponentAccessor.applicationProperties.getString(APKeys.JIRA_BASEURL)
@Field defaultProjectTemplate = "MFGS"
@Field adminUserName = 'admin'

copyProject(httpMethod: "POST", groups: ["jira-administrators", "support-project-creators"]) { MultivaluedMap queryParams, String body ->
def bodyObject = new JsonSlurper().parseText(body)
if (!body) {
return Response.serverError().entity([success: false, output: ["No data supplied: targetKey and targetName are required"]]).build()

def sourceKey = bodyObject.sourceKey
def targetKey = bodyObject.targetKey
def targetName = bodyObject.targetName
def projectLead = bodyObject.projectLead

if (!sourceKey) {
sourceKey = defaultSupportProjectKey

def inputValidationResults = validateInputs(sourceKey, targetKey, targetName, projectLead)
if (inputValidationResults.errors) {
return Response.serverError().entity([success: false, output: inputValidationResults.errors]).build()
} else {
def projectCopyResults = copyProject(sourceKey, targetKey, targetName, projectLead)
if (projectCopyResults.success) {
return Response.created(new URI(
} else {
return Response.serverError().entity(projectCopyResults).build()

* Validate the input parameters
* @param sourceKey
* @param targetKey
* @param targetName
* @param projectLead
* @return map with errors array. The array will be empty if the validation rules are all passed
def validateInputs(sourceKey, targetKey, targetName, projectLead) {
def errors = []
if (!targetKey || !targetName) {
errors << "Missing parameter: targetKey and targetName are required"
if(!(targetKey ==~ /[A-Z][A-Z0-9]+/)){
errors << "Invalid parameter: targetKey ($targetKey) must start with a letter and contain only uppercase letters or numbers."
def sourceProject = projectManager.getProjectObjByKeyIgnoreCase(sourceKey)
if (!sourceProject) {
errors << "Invalid parameter: sourceKey ($sourceKey) provided is not a valid Jira project."
def targetProject = projectManager.getProjectObjByKeyIgnoreCase(targetKey)
if (targetProject) {
errors << "Invalid parameter: targetKey ($targetKey) provided already exists. Choose a different key"
targetProject = projectManager.getProjectObjByName(targetName)
if (targetProject) {
errors << "Invalid parameter: targetName ($targetName) provided is already in use for project with key=$targetProject.key. Choose a different name"
if (projectLead) {
def projectLeadUser = ComponentAccessor.userManager.getUserByKey(projectLead)
if (!projectLeadUser || !projectLeadUser.isActive()) {
errors << "Invalid parameter: projectLead ($projectLead) provided is not a valid Jira user"
return [errors: errors]

* Call the scriptRunner Built-in script as the 'admin' with some default parameters to create an empty copy of the project.
* Note that the copyscript handles custom field contexts
* Additionally, we can set the default projectLead
* @param sourceKey The project that will be the source of the copy
* @param targetKey The desired key for the new project
* @param targetName The desired name for the new project
* @param projectLead The username of the desired project lead
* @return
def copyProject(sourceKey, targetKey, targetName, projectLead) {
def copyProject = new CopyProject()
def inputs = [
(CopyProject.FIELD_SOURCE_PROJECT) : sourceKey,
(CopyProject.FIELD_TARGET_PROJECT) : targetKey,
(CopyProject.FIELD_TARGET_PROJECT_NAME) : targetName,
(CopyProject.FIELD_COPY_VERSIONS) : false,
(CopyProject.FIELD_COPY_COMPONENTS) : false,
(CopyProject.FIELD_COPY_ISSUES) : false,

def errorCollection = copyProject.doValidate(inputs, false)
if (errorCollection.hasAnyErrors()) {
log.warn("Couldn't create project: $errorCollection")
return [success: false, output: errorCollection]
} else {
def output = copyProject.doScript(inputs)
def newProject = projectManager.getProjectObjByKeyIgnoreCase(targetKey)
if (!newProject) {
return [success: false, output: output]
if (newProject && projectLead) {
projectManager.updateProject(newProject, targetName, newProject.description ?: "", projectLead, newProject.url ?: "", newProject.assigneeType)
newProject = projectManager.getProjectObjByKeyIgnoreCase(targetKey)
return [success: true, output: "Project [$targetKey] created as a copy of [$sourceKey] with project lead '$'", link: "$baseUrl/projects/$targetKey"]

The validateInputs may seem redundant because you still have to call the copyProject.doValidate() method. But in my case, those same input parameters were being used by several endpoints and it was cleaner to validate them upfront and provide some simple hints to the users.

You can also see this other question/answer that shows how you might implement this in a post-function.

1 vote
Answer accepted
Anton Chemlev - Toolstrek -
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
August 8, 2019

Suggest an answer

Log in or Sign up to answer