This page gives some guidance on how to use ScriptRunner to work with Tempo Timesheets for JIRA, to accomplish some basic scripting requirements, such as:

The cornerstone of this is the two annotations discussed in Scripting Other Plugins, namely @WithPlugin and @PluginModule .

Let’s dive straight into the examples.


Summing worklogs with an attribute

Tempo allows you to categorise worklogs with additional attributes. Let’s say we have a checkbox attribute called Overtime, and we’d like to display the total overtime on each issue.

We configure the Overtime attribute, as in the following example:


Set up a custom field, in this example I’m going to call it Total Overtime.

The custom field script is

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.worklog.Worklog
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import com.tempoplugin.worklog.attribute.WorklogAttributeService


WorklogAttributeService worklogAttributeService

def worklogManager = ComponentAccessor.getWorklogManager()
def worklogs = worklogManager.getByIssue(issue)

worklogs.findAll { worklog ->
.sum { Worklog worklog ->
} as Long

// if no overtime worklogs just return null

Use a custom template so that the duration is presented nicely, but note that we return a Long value so the duration searcher can index it properly:

Custom Template
#if ($value)




Duration Searcher

If configured properly we should be able to search for issues where the overtime has exceeded 4 hours using: "Total Overtime" > 4h.

Automatically adding a worklog

In this example we will automatically create a worklog when an issue is transitioned out of some state. Let’s imagine that in our workflow all issues raised by customers go through a Triage state. When the triage is complete we want to automatically log the amount of time it took us to triage the issue. Whether that’s billable or not is not in scope of this example!

This is a script workflow post-function, the code is:

import com.atlassian.jira.component.ComponentAccessor
import com.onresolve.scriptrunner.runner.ScriptRunnerImpl
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import is.origo.jira.plugin.common.TempoWorklogManager
import com.tempoplugin.core.datetime.api.TempoDateTime


TempoWorklogManager tempoWorklogManager

def currentUser = ComponentAccessor.getJiraAuthenticationContext().getUser()
def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()

// calculate the time we entered this state
def changeHistoryItems = changeHistoryManager.getAllChangeItems(issue).reverse()
def timeLastStartProgress = changeHistoryItems.find {
    it.field == "status" && it.toValues.values().contains("In Triage") //

def attributes = ["_Worktype_" : "TRIAGE"] // set the Tempo Worktype attribute

def timeInProgress = new Date().time - timeLastStartProgress.time // this is in millis
def timeToLog = Math.round(timeInProgress.longValue() / 1000) // so round to seconds
def remaining = issue.estimate ? (issue.estimate - timeToLog) : 0 // calc remaining estimate

tempoWorklogManager.create(issue.key,, "Auto-created worklog for triage", attributes, timeToLog, 0, remaining, currentUser, ScriptRunnerImpl.PLUGIN_KEY)

So, when we execute the action that takes us out of the In Triage state, e.g. Triage Complete, a worklog is added, and the Tempo drop-down called Work type is set to Triage.

You could query on issues that have had X hours logged to the Triage work type using a similar script field as above.

Population of the dropdown

Tempo requires the keys and values for the drop-down in JSONP format. We can provide this easily using a script REST endpoint. Note you can customise the available options based on the query parameters.

import groovy.json.JsonBuilder
import groovy.transform.BaseScript


@BaseScript CustomEndpointDelegate delegate

tempoWorkType(httpMethod: "GET") { MultivaluedMap queryParams, String body ->

    def callbackFn = queryParams.getFirst("callback")

    def options = [
        values: [
                value:"Please select..."
                value:"Quality Assurance"

    def resp = """${callbackFn} ( ${new JsonBuilder(options).toPrettyString()} )""".toString()

    return Response.ok(resp).build()