Script Jobs allow you to run your own code at regular intervals. Some examples are:

It is not advisable to use this on Data Center at the moment, as currently it will run on multiple cluster nodes.
Most Script Jobs take a Cron Expression to define the interval a job will be run at. If you’re unfamiliar with cron expressions then we recommend you use the utility here to help you build your own.

Deactivate Users

This scheduled job deactivates particular users at a specified date and time, preventing further access to Bitbucket Server.

Over time the people in your organization who can access Bitbucket Sever can change. For example someone leaves your company or someone in a different department is given temporary access to Bitbucket Server.

After this happens, an administrator usually has to deactivate the user manually. Scheduling the deactivation in advance can be helpful for Bitbucket admins with already jam-packed todo lists; as soon as they know the date when a user should lose access, they can schedule it and know it will happen automatically. This can free up unused seats on your license and keep you compliant with your organization’s policies and relevant regulations.

To deactivate the users all their global permissions are removed, including any groups they are a part of which will completely remove their access to Bitbucket. Unfortunately at this time Bitbucket does not provide a way to deactivate users using their API. Please see BSERV-8817 for further details on this.

In the example below we will deactivate User and Contractor on 17th August 2018 at 9am. In the notes section we have even referenced a JIRA issue key ACCESS-1234 so we can refer back to who reported this change.

deactivate users

Clicking Run now will run the job right now, so you can test (on a development instance ideally). Otherwise clicking Add will schedule the job to be run at the specified date.

After we have added the job and it has run it will be marked as expired, hovering over the expiry time will show the exact time the job was run. This means we can now go ahead and remove this job from the list as in the example below:

deactivate users expired

Send mail

This scheduled job will automatically send email notifications periodically based on a condition and email template. This is particularly useful if you want a daily summary of what has happened in Bitbucket.

You will just need to configure the interval and then provide a subject and an email template, as well as the email format (plain text or HTML) and a list of recipients.

List all public repositories

This example sends an email notification listing all the public repositories present in your Bitbucket Server instance.

The email format selected should be Plain text.

You can use the following code below in the Condition and Configuration section:

import com.atlassian.bitbucket.repository.Repository
import com.atlassian.bitbucket.repository.RepositorySearchRequest
import com.atlassian.bitbucket.repository.RepositoryService
import com.atlassian.bitbucket.util.Page
import com.atlassian.bitbucket.util.PageProvider
import com.atlassian.bitbucket.util.PageRequest
import com.atlassian.bitbucket.util.PagedIterable
import com.atlassian.sal.api.component.ComponentLocator

import javax.annotation.Nonnull

import static com.atlassian.bitbucket.repository.RepositoryVisibility.PUBLIC

def repositoryService = ComponentLocator.getComponent(RepositoryService)

def publicRepositories = new PagedIterable<Repository>(new PageProvider<Repository>() {
    Page<Repository> get(@Nonnull PageRequest pageRequest) { RepositorySearchRequest.Builder().visibility(PUBLIC).build(), pageRequest) (1)
}, new PagedIterable(0, 100)) as Iterable<Repository>

def sb = new StringBuilder()
publicRepositories.each { repository ->
    sb << "$repository.project.key/$repository.slug\n" (2)

def publicReposContent = sb.toString()
config.content = publicReposContent (3)

! publicReposContent.isEmpty() (4)
As this is a condition the email will only be sent if there are public repositories in your Bitbucket instance.
1 We find all public repositories
2 For each public repository we build a string containing the project key and repository slug
3 We use the config variable in the binding to store the content for use in the template below (see additional configuration section for details)
4 The condition will evaluate to true if we have any private repositories and then the email will be sent

The subject template:

List of public repositories:

The body template:

These repositories are public, you should review them:


The result should look similar to the following:

These repositories are public, you should review them:


Additional Configuration in Emails

You may notice the syntax for getting content in the template 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.

A useful example of a Condition and Configuration section that defines config variables for all pull requests that have been merged in the last 24 hours is:

import com.atlassian.bitbucket.nav.NavBuilder
import com.atlassian.bitbucket.pull.PullRequest
import com.atlassian.bitbucket.pull.PullRequestSearchRequest
import com.atlassian.bitbucket.pull.PullRequestService
import com.atlassian.bitbucket.pull.PullRequestState
import com.atlassian.bitbucket.repository.RepositoryService
import com.atlassian.bitbucket.util.*
import com.atlassian.sal.api.component.ComponentLocator
import groovy.xml.MarkupBuilder

import javax.annotation.Nonnull

def pullRequestService = ComponentLocator.getComponent(PullRequestService)
def repositoryService = ComponentLocator.getComponent(RepositoryService)

def projectKey = "PROJECT_1"
def repoSlug = "rep_1"

def repo = repositoryService.getBySlug(projectKey, repoSlug)

def yesterday = new Date().minus(1)

def pullRequestSearchRequest = new PullRequestSearchRequest.Builder()

def mergedPullRequests = new PagedIterable<PullRequest>(new PageProvider<PullRequest>() {
    Page<PullRequest> get(@Nonnull PageRequest pageRequest) {, pageRequest)
}, new PageRequestImpl(0, 100)) as Iterable<PullRequest>

def navBuilder = ComponentLocator.getComponent(NavBuilder)

def writer = new StringWriter()
def builder = new MarkupBuilder(writer)

builder.ul {
    mergedPullRequests.each { PullRequest pullRequest ->
        def url = navBuilder.repo(repo).pullRequest( {
            a(href: url, pullRequest.title)

config.content = writer.toString()
config.numberMerged = mergedPullRequests.size()

mergedPullRequests.size() > 0
You will have to change the projectKey and repoSlug variable above to match your own. The email format selected should be HTML.

The subject template:

$numberMerged PRs have been merged today

The body template:

The following pull requests were merged today:


As a result of that configuration, an email will be sent to the selected email addresses with a content similar to the following one:

send mail job result

Clicking on the links will take you to that pull request in Bitbucket.

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

def condition = repository.slug == "rep_1" && ...
if (! condition) return false

def expensiveConfigVars = [foo: "bar"]

config.putAll (expensiveConfigVars)

Max repository size notification

This scheduled job helps you to identify all of the repositories that exceed a particular size by receiving an email listing all of the repositories that exceed a particular size.

You can find more details here about how a large repository negatively impacts the performance of Git on Bitbucket Server.

This is particularly useful as repositories grow larger and you want to identify the ones that require maintenance before they have a negative impact on your instance as detailed above.

Once you’ve identified the repositories you can use some of the approaches here to reduce the size of the repository.

Some of the identified repositories could be good candidates for having their files moved to Git LFS.

The size of the repository checked does NOT include files under Git LFS.

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.