Heads up! ScriptRunner documentation is moving to docs.adaptavist.com. Adaptavist will keep this site up for a bit, but no future updates to documentation will be published here. ScriptRunner 6.20.0 will be the last release to link to scriptrunner.adaptavist.com for in-app help.

ScriptRunner allows you to respond to Confluence events, via an inline script or a pointer to a file. These responses are called event listeners.

Example event listeners follow:

Adding an Event Listener

  1. Navigate to Confluence Administration by selecting the Cog button and then selecting General Configuration.

  2. In the left menu bar, under ScriptRunner, navigate to Script Event Handlers.

  3. Click Add New Item.

  4. Choose the Custom Event Listener link to use your own scripts to respond to events.

  5. Fill out the following fields:

    • Note: (Optional) Add a note for your reference.

    • Events: Start typing to find the event to listen for.

    • One of the following fields are required for each event listener:

      • Script File: Enter the path accessible to the file on the server.

      • Inline Script: Enter any necessary inline script.

Some of the built-in listeners have a default event that they listen for (that cannot be changed) and, as such, do not not have an Events field.
add event handler

Browse Event Listener Functionality

After you click Create Listener, a search bar appears that allows you to Search ScriptRunner Functionality. Use this search bar to search for available event listeners.

Search Bar

For example, if you’re looking for an event listener that works remotely you could type "Remote" and press Enter. Then, the list of event listeners is narrowed down to only those containing the word "remote" in their title or description.

Built-In Event Listeners

Inherit Restrictions for Pages

Use Inherit Restrictions for Pages to create pages that inherit the parent page restrictions.

In Confluence, by default, view restrictions are inherited. This means that a view restriction applied to one page will cascade down to any child pages. Edit restrictions are not inherited, which means pages need to be restricted individually.

Inherit Restrictions for Pages overcomes this default and offers administrators the option to specify which spaces should automatically inherit all restrictions.

Follow these steps to configure the listener:

  1. Fill out the Location field:

    • Choose All spaces to inherit restrictions in all spaces across the instance

    • Choose Select spaces to inherit restrictions only on a subset of spaces

      • Upon choosing this option the listener will display a space picker in which multiple spaces can be selected

  2. Select Add.

The Inherit Restrictions for Pages listener considers only the immediate parent page restrictions and applies them to newly created pages. The listener does not apply cumulative restrictions from all the ancestors of a page.

Read more about page restrictions in the Confluence Page Restrictions documentation.

Working with Custom Event Listeners

The script binding shares information between the script and the application. An event listener variable in the script binding is provided. This variable corresponds to the event which triggered the listener. For example, if you are listening for PageCreateEvent the event variable will be a PageCreateEvent object.

You can choose to have your handler listen for multiple events. If you need to do different things depending on the type of event, you can check that with instanceof. Alternatively, you can type the event variable to the most specific superclass. In the previous example, that would be PageEvent.

In the following example, the event is getting page content for both PageCreateEvent and PageUpdateEvent. Since PageEvent is a common superclass for both PageCreateEvent and PageUpdateEvent, you could cover both events by using the following code:

def event = event as PageEvent(1)
def content = event.content.bodyAsString

// do something with the page content
1 - The event is passed in the binding. This line is only used to give type information when using an IDE, and has no functional impact.


Add a Comment on Page Create

Some organisations have a particular style guide or would like to enforce specific rules. This event listener example looks at the content of new pages for banned words. If the page content contains any of a list of banned words, a comment is automatically added with an alternative suggestion. The following image is a comment generated in response to a banned word:

draft story

The code for this event listener follows:

import com.atlassian.confluence.event.events.content.page.PageEvent
import com.atlassian.confluence.pages.CommentManager
import com.atlassian.sal.api.component.ComponentLocator

def event = event as PageEvent
// use event.getPage().getSpace() if you want to restrict only to certain spaces

def commentManager = ComponentLocator.getComponent(CommentManager)
def body = event.content.bodyAsString

def alternatives = [
    "air hostess": "flight attendant",
    "amuck"      : "amok",

def commentBody = alternatives.findAll { badWord, goodWord ->
}.collect { badWord, goodWord ->
    "<li><b>${badWord}</b> should be avoided. Consider using: <b>${goodWord}</b>.</li>"

if (commentBody) {
    commentManager.addCommentToObject(event.content, null, "<p>Please consider the following issues: <ul>$commentBody</ul> </p>")

In practice, you would also want to watch page updates and only look at the diff between old and new versions.

Add Inline Comment on Page Create

Similar to Add a Comment on a Page Create, you can configure your listener to add inline comments instead. As you get these comments, you can dismiss them. The following image is an example of an inline comment:

draft story 2

The code for this event listener follows:

import com.atlassian.confluence.event.events.content.page.PageEvent
import com.atlassian.confluence.setup.settings.SettingsManager
import com.atlassian.sal.api.component.ComponentLocator
import groovy.json.JsonBuilder
import groovyx.net.http.HTTPBuilder
import groovyx.net.http.Method
import net.sf.hibernate.HibernateException
import net.sf.hibernate.Session
import net.sf.hibernate.SessionFactory
import org.apache.http.HttpRequest
import org.apache.http.HttpRequestInterceptor
import org.apache.http.protocol.HttpContext
import org.apache.log4j.Level
import org.apache.log4j.Logger
import org.springframework.orm.hibernate.SessionFactoryUtils

import java.sql.SQLException

import static groovyx.net.http.ContentType.JSON

log = Logger.getLogger("com.test.InlineScript")

event = event as PageEvent

def settingsManager = ComponentLocator.getComponent(SettingsManager)

baseUrl = settingsManager.getGlobalSettings().getBaseUrl()

def alternatives = [
    "air hostess": "flight attendant",
    "amuck"      : "amok",

Session s = SessionFactoryUtils.getSession(ComponentLocator.getComponent(SessionFactory), false)

def flushAndCommitSession(Session s) {
    // commit any open transaction to release any locks, as the tables get deleted
    // via another connection
    if (s != null) {
        try {
            log.info("Flushing session and committing pending transactions")
            log.info("Session flush and commit complete")
        } catch (HibernateException he) {
            log.error("error flushing session", he)
        } catch (SQLException sqle) {
            log.error("error committing connection", sqle)

 * find occurrence of word in a String
Integer keyFinder(String pageBody, String key) {
    def keyToFind = /$key/
    def keyFinder = (pageBody =~ /$keyToFind/)

 * Recursive function for creating inline comments
def createInlineComments(
    Map<String, String> altMap, String pageContent, Integer matches = 0, Integer index = 0, String key = ""
) {

    if (matches != 0 && matches == index && key) {
        //loop breakout condition or reset variables when key changes
        if (altMap.size() == 0) {

        def keys = altMap.keySet() as List
        key = keys.pop()
        matches = keyFinder(pageContent, key)
        index = 0
    } else {
        //function entry point match count
        matches = keyFinder(pageContent, key)
    if (altMap.size() == 0) {

    if (matches) {
        log.debug("Found key : ${key}")
        def data = [
            containerId         : "${event.page.getId()}",
            parentCommentId     : 0,
            numMatches          : matches,
            matchIndex          : index,
            body                : "<p>Please use <b>${altMap.get(key)}</b> instead</p>",
            originalSelection   : key,
            serializedHighlights: ""

        def jsonFormattedData = new JsonBuilder(data).toString()
        def http = new HTTPBuilder(baseUrl)

        http.client.addRequestInterceptor(new HttpRequestInterceptor() {
            void process(HttpRequest httpRequest, HttpContext httpContext) {
                httpRequest.addHeader('Authorization', 'Basic ' + 'admin:admin'.bytes.encodeBase64().toString())
                httpRequest.addHeader('X-Atlassian-Token', "no-check")

        http.request(Method.POST, JSON) {
            uri.path = "/confluence/rest/inlinecomments/1.0/comments"
            requestContentType = JSON
            body = jsonFormattedData

            response.success = { resp ->
                log.debug("RESULT STATUS:  DONE")
                //recursive call to the function
                createInlineComments(altMap, pageContent, matches, index + 1, key)

            response.failure = { resp ->
                log.debug("FAILED : ${resp.statusLine.statusCode}")
    } else {
        if (altMap.size() == 0) {
        createInlineComments(altMap, pageContent, 0, 0, (altMap.keySet() as List).pop())

createInlineComments(alternatives, event.content.bodyAsString, 0, 0, (alternatives.keySet() as List).pop())

Create Page when User Created

This event listener example automatically creates a user profile page in the Team space. You can use this profile page to list their skills and profile. The following image is an example of a profile page:

create user

The event selected for this example is UserCreateEvent, and the code follows:

import com.atlassian.confluence.core.DefaultSaveContext
import com.atlassian.confluence.event.events.user.UserCreateEvent
import com.atlassian.confluence.pages.Page
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.spaces.SpaceManager
import com.atlassian.confluence.user.ConfluenceUser
import com.atlassian.sal.api.component.ComponentLocator
import groovy.xml.MarkupBuilder

try {
    def event = event as UserCreateEvent
    def user = event.user as ConfluenceUser
    def pageManager = ComponentLocator.getComponent(PageManager)

    def spaceManager = ComponentLocator.getComponent(SpaceManager)
    def teamSpace = spaceManager.getSpace("TEAM")

    def writer = new StringWriter()
    def builder = new MarkupBuilder(writer)
    builder.table {
        tbody {
            tr {
                td {
                    "ac:link" {
                        "ri:user"("ri:userkey": user.key)
            tr {
            tr {

    def parentPage = teamSpace.getHomePage()
    assert parentPage

    def targetPage = new Page(title: "About ${user.fullName}",
        bodyAsString: writer.toString(),
        space: teamSpace,
        parentPage: parentPage
    pageManager.saveContentEntity(targetPage, DefaultSaveContext.DEFAULT)
    pageManager.saveContentEntity(parentPage, DefaultSaveContext.MINOR_EDIT)
catch (anyException) {
    log.warn("Failed to create page for new user", anyException)

Collecting Stats

This event listener example automatically sends statistics to statsd for page views, space views, and users/pages views. The following image is an example of the statistics for page views:

page views statsd

In the example, Grafana is used to visualise page views metrics.

The event selected for this example is PageViewEvent, and the code follows:

import com.atlassian.confluence.event.events.content.page.PageEvent
import com.atlassian.confluence.user.AuthenticatedUserThreadLocal
import groovy.transform.Field

@Field final def host = ""
@Field final def port = 8125

def event = event as PageEvent
def currentUser = AuthenticatedUserThreadLocal.get()

// keys to create unique nodes for counters
def spaceKey = event.page.spaceKey
def pageId = event.page.id as String
def userKey = currentUser.name
def nodeId = "confluence.stats.views"

// build the unique metric keys
def pageViewMetricKey = "${nodeId}.page.${pageId}"
def spaceViewMetricKey = "${nodeId}.space.${spaceKey}"
def userViewMetricKey = "${nodeId}.user.${userKey}.${pageId}"

// increase by one the counters for the following metric keys
increaseByOne(pageViewMetricKey, userViewMetricKey, spaceViewMetricKey)

def increaseByOne(String... keys) {
    def dataToSend = ""
    def value = 1 //increase counter by one

    //syntax for counter according to https://github.com/etsy/statsd/blob/master/docs/metric_types.md
    for (key in keys) {
        dataToSend += "${key}:${value}|c\n"

    def data = dataToSend.getBytes()
    def address = InetAddress.getByName(host as String)
    def packet = new DatagramPacket(data, data.length, address, port as int)
    def socket = new DatagramSocket()
    try {
    } finally {

Creating a Jira Project Whenever a Confluence Space Is Created

This event listener example automatically creates a Jira project every time a space is created.

The event selected for this example is SpaceCreateEvent, and the code follows:

import com.atlassian.applinks.api.ApplicationLinkService
import com.atlassian.applinks.api.application.jira.JiraApplicationType
import com.atlassian.confluence.event.events.space.SpaceCreateEvent
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.sal.api.net.Response
import com.atlassian.sal.api.net.ResponseException
import com.atlassian.sal.api.net.ResponseHandler
import groovy.json.JsonBuilder

import static com.atlassian.sal.api.net.Request.MethodType.POST

def appLinkService = ComponentLocator.getComponent(ApplicationLinkService)
def appLink = appLinkService.getPrimaryApplicationLink(JiraApplicationType)
def applicationLinkRequestFactory = appLink.createAuthenticatedRequestFactory()

def event = event as SpaceCreateEvent
def space = event.space

def input = new JsonBuilder([
    projectTypeKey    : "business",
    projectTemplateKey: "com.atlassian.jira-core-project-templates:jira-core-task-management",
    name              : space.name,
    key               : space.key,
    lead              : event.space.creator.name,

def request = applicationLinkRequestFactory.createRequest(POST, "/rest/api/2/project")
    .addHeader("Content-Type", "application/json")

request.execute(new ResponseHandler<Response>() {
    void handle(Response response) throws ResponseException {
        if (response.statusCode != 201) {
            log.error("Creating jira project failed: ${response.responseBodyAsString}")

Creating a Page When a Form Is Submitted

This event listener example automatically creates a new page when a form is submitted.

If you have Forms for Confluence installed, you can listen for the FormSubmitEvent and run a script to create a new page. You can specify the space, title, and page content of the new page.

Follow these steps to create a new page when a user submits an internal feature request or events proposal using Forms for Confluence.

  1. Create a new form that includes input fields to collect a specified pageTitle, spaceKey, and pageContent.

    You can also include additional fields, like an attachment.

  2. Configure the event listener for FormsSubmitEvent where the spaceKey, pageTitle, and pageContent corresponds to the name parameter defined for the macros in your form.

    When the form is submitted, a new page is created with the values entered by the user.

    form submit event sr screen

    The code for this example follows:

import com.atlassian.confluence.core.DefaultSaveContext
import com.atlassian.confluence.pages.Page
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.spaces.SpaceManager
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.xwork.FileUploadUtils
import groovy.json.JsonSlurper

def spaceManager = ComponentLocator.getComponent(SpaceManager)
def pageManager = ComponentLocator.getComponent(PageManager)

def eventFormSubmission = binding.variables.get("event")

String eventData = eventFormSubmission.structuredValue

//  any uploaded files. ie - uploaded using the "Forms - Attachment" macro
List<FileUploadUtils.UploadedFile> uploadedFiles = eventFormSubmission.uploadedFiles

// Space key, page title, page content, in a map.
Map<String, String[]> inputtedValues = new JsonSlurper().parseText(eventData) as Map<String, String[]>

def parentPageTitle = getValue(inputtedValues, "parentPageTitle")
def pageTitle = getValue(inputtedValues, "pageTitle")
def spaceKey = getValue(inputtedValues, "spaceKey")
def pageContent = getValue(inputtedValues, "pageContent")

// construct a new page
Page targetPage = constructNewPage(spaceManager, spaceKey, pageTitle, pageContent)

validateSpace(spaceKey, spaceManager)
validateParentPage(spaceKey, parentPageTitle, pageManager)

setPageAncestryAndSave(parentPageTitle, targetPage, spaceKey, pageManager)

private static String getValue(Map<String, String[]> data, String key) {
    if (!data.get(key) || data.get(key)[0].isEmpty()) {
        throw new IllegalArgumentException("A \"" + key + "\" was not provided.")
    } else if (hasMultipleUniqueEntries(data.get(key) as List<String>)) {
        throw new IllegalArgumentException("multiple \" " + key + "\"'s were provided, please enter a single \"" + key + "\"")

private static boolean hasMultipleUniqueEntries(List<String> entries) {
    Set uniqueEntries = [] as Set
    uniqueEntries.size() != 1

private static Page constructNewPage(SpaceManager spaceManager, String spaceKey, String pageTitle, String pageContent) {
    def targetPage = new Page(
        space: spaceManager.getSpace(spaceKey),
        title: pageTitle,
        bodyAsString: pageContent,

private static validateSpace(String spaceKey, SpaceManager spaceManager) {
    def space = spaceManager.getSpace(spaceKey)
    if (space == null) {
        throw new IllegalArgumentException("invalid space key")

private static validateParentPage(String spaceKey, String parentPageTitle, PageManager pageManager) {
    def parentPage = pageManager.getPage(spaceKey, parentPageTitle)
    if (parentPage == null) {
        throw new IllegalArgumentException("invalid parentPageTitle. " + parentPageTitle + " is not found in " + spaceKey)

private static setPageAncestryAndSave(
    String parentPageTitle, Page targetPage, String spaceKey, PageManager pageManager
) {
    Page parentPage = pageManager.getPage(spaceKey, parentPageTitle)

    pageManager.saveContentEntity(parentPage, DefaultSaveContext.DEFAULT)
    pageManager.saveContentEntity(targetPage, DefaultSaveContext.DEFAULT)


The following image is the resulting form:


The following image is the resulting page that is created when the form is submitted:

This event listener is not directly tied to Forms configuration. The results of the Forms configuration can be recorded and/or sent to a different destination that you choose.

Have questions? Visit the Atlassian Community to connect, share, and learn with other Atlassian users and experts, including Adaptavist staff.

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

Want to learn more? Check out courses on Adaptavist Learn, an online platform to onboard and train new users for Atlassian solutions.