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 Jobs allow you to run your own code at regular intervals. Some examples are:

There is a built-in job type that allows you to iterate over content defined by a CQL query, and do something with each hit.

Browse Jobs Script Functionality

You can use the Serach ScriptRunner Functionality search bar to search the available jobs.

jobs browse functionality

For example, if you’re looking for a job that gives you notifications, you could type "Notifications" and press Enter. Then, the list of jobs is narrowed down to only those containing the word "notifications" in their title or description.

Job Options

The following sections explain the available Jobs options:

  • CQL Escalation Service

  • Prune Old Page Versions

  • Custom Scheduled Job

  • Old Content Notifier

CQL Escalation Service

There is a specialised case of a job, where the results of a CQL query are passed to the code you provide. It saves you having to write the code to execute the query, and set the impersonation context.

In the script binding a variable hits is available, which is an Iterable<ContentEntityObject>. Therefore you can do something with each piece of content returned using:

hits.each { content ->
    // do something with content

Depending on your query, the type of content in the above example will be either a Page, an Attachment, a BlogPost, or a Comment etc.

However, it will always be a ContentEntityObject.

If you only want to do something with pages, then use a query that only selects pages, eg space = ds and type = page.

Alternatively, you can use instanceof in your script to do different things depending on the type of content.

You should specify a user that has permission to see the content that you want.
Your service code is automatically wrapped into a transaction template, but you should take care your service doesn’t not run for very long, unless it’s at a quiet time, e.g. the weekend.

Custom Scheduled Job

You can use this option to create your own scheduled jobs. The following two sections are examples of jobs that you could create.

Add Label to Outdated Pages

As a worked example, we’ll add a label pages that haven’t been modified for one month in a particular space. You may do this if you need to ensure that content is kept current, although this solution is deliberately over-simplistic.

add label job config

The query here selects pages (and only pages, not attachments, blog posts, comments etc) in the demonstration space that have not been modified for over 4 weeks. Look at the examples and refer to the CQL documentation if you need help.

Note that the number of hits from the query is shown. You should be careful that you don’t inadvertently select more content than you intended to. The user to run the query as is also shown highlighted.

Clicking Run now will run the service right now, so you can test (on a development instance ideally).

The content of the file in the screenshot is below - you can either copy it as an inline script, or put it on a file under a script root.

import com.atlassian.confluence.labels.Label
import com.atlassian.confluence.labels.LabelManager
import com.atlassian.confluence.labels.Namespace
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.scheduler.JobRunnerResponse

def labelManager = ComponentLocator.getComponent(LabelManager)
hits.each { page -> (1)

    def labelName = "requires-review"

    def label = labelManager.getLabel(labelName)
    if (!label) {
        label = labelManager.createLabel(new Label(labelName, Namespace.GLOBAL))

    if (!page.labels*.name.contains(labelName)) {
        log.info("Add label to page: ${page.title}")
        labelManager.addLabel(page, label) (2)

return JobRunnerResponse.success()
1 iterate over the items returned from the CQL
2 add label to the page

Add Comment to Outdated Pages

Alternatively you may wish to add a comment to any page that has not been modified for over three months, and has had no comment in over three months. This is very similar to the example above so refer to that for any missing information.

The query would be:

space = ds and type = page and lastModified < now("-14w")
CQL does not support searching on months or years, so multiply the weeks value appropriately.

The code is:

import com.atlassian.confluence.pages.CommentManager
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.scheduler.JobRunnerResponse

def commentManager = ComponentLocator.getComponent(CommentManager)

hits.each { page ->

    if (!commentManager.getPageComments(page.id, new Date() - 98)) { (1)
        log.debug("Adding 'old page' comment to page: ${page.title}")
        commentManager.addCommentToObject(page, null, "Old page! Please review")

return JobRunnerResponse.success()
1 check we don’t have a comment added within the last three months (in days)

Prune Old Page Versions

Over time, the number of revisions for each page will grow. Do you really need versions that were created over two years ago? Given that Confluence stores the entire content for each revision (rather than deltas), this can dramatically increase your database storage requirements.

This job will allow you to automatically prune old page versions according to your requirements, for example:

  • On all spaces, remove versions older than three months, but always keep the latest three versions

  • On spaces where you may have sensitive content, only keep the latest revision

Use the Page Version Age to determine what items will be deleted. The Preset Filters are:

  • Older than 6 months

  • Older than 1 year

  • Older than 2 years

    You also have the option to select a Custom Filter, Older Than. If you select the Older Than value, a screen appears where you choose inputs.

You should specify a user that has permission to edit the pages you want to prune. Specifying an admin account does not necessarily mean all pages will be pruned. CQL respects permissions even against an admin account.
This will irretrievably remove versions according to your rules. They cannot be restored without doing a full space or Confluence restore. Make sure you have tested on your staging instance. At the very least start with a CQL query that selects just test content.


Start by writing CQL to select the content you are interested in. See the provided examples to select only content from particular spaces etc.

Next specify your parameters for which versions to remove. You must enter at least one of the following, but you can provide both:

  • Minimum number of versions to retain. If no Older than days is provided, it will just delete all versions whilst retaining the latest X

  • If Older than days is provided on its own, all versions older than this number will be removed. If you also provide a minimum number of versions to retain, it will delete the older versions, but only those over the minimum number to retain

For example, to remove versions older than 90 days, but ensure there are always two historical versions, you may use this configuration:

prune content hr
It’s possible to have a general configuration for all spaces, for example, keep all versions made within the last three months and a minimum of three, but more restrictive settings for certain spaces. You would do this simply by setting up two jobs.
This currently only works for blog posts and pages.
The most recent version will never be deleted.

Old Content Notifier

This script will check the pages and their descendants returned from the CQL clause provided to determine if there are any inactive pages. If any inactive pages were found a short report will be generated and emailed to all the users in the specified group.

An inactive page is defined as a page of a specified age where all descendants are also the same age or older. Adding a comment to a page will flag it as active.

This scheduled job will allow you to automatically notify content managers about inactive pages, for example:

  • On a specified space, find all the inactive pages older than 2 years with inactive descendants without the label 'ignore_inactive'. The CQL clause would look like this: space = SPACEKEY and type = page and lastModified < now('-104w') and label not in (ignore_inactive)

Use the Content Age field to determine what items will be deleted. The Preset Filters are:

  • Older than 6 months

  • Older than 1 year

  • Older than 2 years

    You also have the option to select a Custom Filter, Older Than. If you select the Older Than value, a screen appears where you choose inputs.


Start by writing CQL to select the pages you are interested in. See the provided examples on the CQL field in the script to select only pages from particular spaces etc.

The job could potentially take a while to complete if casting a large net with your CQL clause.
This job will only operate on pages.
The "Older than weeks" field must contain a value (which specifies a specific date) equal or greater than the date specified in the CQL 'lastModified' clause

The following image shows an old content notifier job which will run as the Admin user each Sunday morning just after midnight. The CQL clause will find all pages in space with space key 'AN' which were last modified over 2 years ago.

old content notifier

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.