Configuring External Dropdown Lists for Worklogs
Configuring external dropdown lists for worklogs requires Tempo Administrator permission.
Third-party add-ons may impact and/or break the functionality of your external dropdown lists.
You can add custom worklog fields (work attributes) to the Log Time dialog box. One type of custom worklog field is a dropdown list that retrieves list items from an external service. These externally sourced dropdown lists are called dynamic dropdowns. Tempo Timesheets fetches the list items from the external service you defined.
External Service Configuration Examples
For Tempo Timesheets to successfully call an external URL, it must have the following:
The interface between Tempo and the external service must be defined using JSONP. Learn more about JSONP here.
The Tempo URL verification:
When Tempo sends a verification request, it includes a randomly generated
tempoVerificationToken
as a query parameter, so the customer’s server must respond with that same token in thex-tempo-verification-token
response header.
Optional: Client secret configuration
Values in the external service are defined based on your organization’s specific rules and logic. Dynamic Dropdown supports all languages and is very flexible. For demonstration purposes, the examples below use JavaScript.
Simplified JSONP Interface Example
Let’s say that you want to add a custom worklog field called Operations to the Log Time dialog box so that users can choose an operation from a dropdown list. Tempo Timesheets sends a request to an external service to fetch the list of operations. A URL is included in the request. In Tempo Timesheets, the URL that is specified for the external service is in the following form:
https://example.dev/operations
Tempo Timesheets automatically appends ?callback=?
to the URL when the request is sent to the external service. For example:
https://example.dev/operations?callback=?
At run-time, a callback parameter is generated (for example, fn
), and the JSONP library replaces the second question mark in the URL with the callback parameter.
The following example demonstrates how to configure an externally sourced dropdown list. It shows how to set up the external service so that, when Tempo Timesheets sends a request, the service returns a key-value list in JSONP (not JSON) format.
Example: Simple JSONP JavaScript with callback named "fn"
fn(
{"values":
[
{
"key":"",
"value":"Please select..."
},
{
"key":"0100",
"value":"This is option ONE"
},
{
"key":"0200",
"value":"And here is option TWO"
}
]
}
)
Function and variables
In the above example, the callback function name is ‘fn'. The external service must use the callback parameter as the callback function name.
‘values’, ‘key’, and ‘value’ in the above example are variables that Tempo Timesheets understands. An array that is called 'values' must be returned and must contain objects with 'key'-'value' pairs.
Complete JavaScript Example
There is no restriction in how you configure your external service. You can use any programming language to develop the script for your external service, using as little or as much complexity as meets your needs. Below is a basic real-world example of what the script behind the external URL can look like if using JavaScript. This example is only intended to show the basic structure for the external service that is called by the URL.
See Configuration Examples for Supported Dynamic Dropdown Items (below) for more detailed examples that illustrate script configuration for each of the supported dynamic dropdown fields, using excerpts from the below example.
Example script
addEventListener('fetch', e => e.respondWith(handleRequest(e.request)));
const ELLIE_IDS = ['557058:8c5fecb6-c617-428e-837d-04a7a8d89981'];
async function handleRequest(request) {
const url = new URL(request.url);
const params = url.searchParams;
const callback = params.get('callback');
const token = params.get('tempoVerificationToken');
// 1) Tempo URL verification token
if (token) {
const h = new Headers();
h.set('X-Tempo-Verification-Token', token);
return new Response('Tempo verification token received', { status: 200, headers: h });
}
if (!callback) {
return new Response('Callback parameter is required', { status: 400 });
}
// 2) Stations based on IssueKey
const parts = url.pathname.replace(/^\/+/, '').split('/').filter(Boolean);
const endpoint = parts[0] || '';
if (endpoint === 'stations') {
const issueKey = parts[1] || params.get('issuekey');
return jsonp(callback, getStations(issueKey));
}
// 3) User (path OR query)
const userRaw = (parts.length > 0 && endpoint !== 'stations')
? parts[0]
: (params.get('user') || '');
// 4) Find first “business” param (ignore callback, token, user)
let fieldName, fieldValue;
for (const [k, v] of params.entries()) {
if (k === 'callback' || k === 'tempoVerificationToken' || k === 'user') continue;
fieldName = k;
fieldValue = v;
break;
}
let values;
// 5a) If only User is passed (no business param) → User rule
if (!fieldName && UserRaw) {
const isEllie = ELLIE_IDS.includes(userRaw);
values = isEllie
? [{ key:'Contractor', value:'Contractor' }]
: [{ key:'Internal', value:'Internal' }];
}
// 5b) If no User AND no business param → Canadian cities
else if (!fieldName && !userRaw) {
values = [
{ key:'Toronto', value:'Toronto' },
{ key:'Alberta', value:'Alberta' },
{ key:'Nova Scotia', value:'Nova Scotia' }
];
}
// 5c) Otherwise show existing field‐based logic
else {
values = getNextOptions(fieldName, fieldValue);
}
return jsonp(callback, values);
}
// IssueKey logic
function getStations(issueKey) {
if (issueKey === 'BVG-1') {
return [
{ key:'Warschauer Straße', value:'Warschauer Straße' },
{ key:'Uhlandstraße', value:'Uhlandstraße' }
];
}
if (issueKey === 'BVG-2') {
return [
{ key:'Mehringdamm', value:'Mehringdamm' },
{ key:'Alt-Mariendorf', value:'Alt-Mariendorf' }
];
}
return [];
}
// Cascading dropdown login: Account + AccountType + SOW
function getNextOptions(field, val) {
switch (field) {
case 'firstAttr':
return ({
'Toronto': [{ key:'Development', value:'Development' }],
'Alberta': [{ key:'QA', value:'QA' }],
'Nova Scotia': [{ key:'Design', value:'Design' }]
}[val] || []);
case 'secondAttr':
return ({
'Development': [
{ key:'Data Center', value:'Data Center' },
{ key:'Cloud', value:'Cloud' }
],
'QA': [
{ key:'Performance Testing', value:'Performance Testing' },
{ key:'Security Testing', value:'Security Testing' }
],
'Design': [
{ key:'Graphic Design', value:'Graphic Design' },
{ key:'UX/UI Design', value:'UX/UI Design' }
]
}[val] || []);
case 'Account':
case 'account':
if (val === 'Billable') {
return [
{ key:'345340', value:'345340' },
{ key:'345350', value:'345350' }
];
} else {
return [{ key:'000000', value:'000000' }];
}
case 'AccountType':
case 'accounttype':
if (val === 'CLIENTA') {
return [
{ key:'Canada', value:'Canada' },
{ key:'USA', value:'USA' }
];
} else if (val === 'CLIENTB') {
return [{ key:'APAC', value:'APAC' }];
} else {
return [{ key:'Global', value:'Global' }];
}
case 'Country':
case 'country':
if (val === 'Canada') {
return [
{ key:'Toronto', value:'Toronto' },
{ key:'Alberta', value:'Alberta' },
{ key:'Nova Scotia', value:'Nova Scotia' }
];
}
return [];
case 'SOW':
case 'sow':
if (val === 'Software Development') {
return [
{ key:'iOS', value:'iOS' },
{ key:'Android', value:'Android' }
];
} else if (val === 'Consulting Services') {
return [
{ key:'Migration', value:'Migration' },
{ key:'Setup', value:'Setup' }
];
} else if (val === 'Training Services') {
return [
{ key:'Enablement', value:'Enablement' },
{ key:'Documentation', value:'Documentation' }
];
} else if (val === 'Research Analysis') {
return [
{ key:'Customer Interviews', value:'Customer Interviews' },
{ key:'Focus Groups', value:'Focus Groups' }
];
}
return [];
default:
return [];
}
}
// JSONP helper
function jsonp(callback, values) {
const body = ${callback}(${JSON.stringify({ values }, null, 2)});
return new Response(body, { headers: { 'Content-Type': 'application/javascript' } });
}
Configuration Examples for Supported Dynamic Dropdown Fields
You can setup the external service to return different values for your dynamic dropdown list, which are supported as follows for select Tempo work attribute types and for select Jira fields:
Jira Issue
Issue (issue key)
User (Jira user)
Custom field
Tempo Account (custom field on the Jira issue)
Tempo Work Attributes (referenced by their key)
Tempo Account
Other dynamic dropdowns (Cascading dynamic work attributes)
These supported fields and attributes can be passed to your external service as either path parameters or query parameters, depending on how your URL and service are structured. For example, an issue key or account ID can be embedded directly in the URL path, or provided as part of the query string. This flexibility allows your script or endpoint to interpret and respond dynamically based on the incoming request format.
The easiest way to retrieve a work attribute key is to go to the Audit Log.
Below are examples that illustrate how each of the above fields can be setup for your external service if you were to use a script. These examples are only intended to illustrate one possible method for the script structure and how it works together with the URL configuration when creating a new dynamic dropdown work attribute.
Jira Fields
Issue (issue key)
URL parameter to use: {IssueKey}
Example URL: https://example.dev/stations/{BVG-1}
Your external service can be configured so that the dynamic dropdown will display different values depending on the issue that users log time to. In this case, the URL parameter that Tempo sends to the external service contains the issue key, as shown above. The relevant excerpt from the example script above is included below.
function getStations(issueKey) {
if (issueKey === 'BVG-1') {
return [
{ key:'Warschauer Straße', value:'Warschauer Straße' },
{ key:'Uhlandstraße', value:'Uhlandstraße' }
];
}
if (issueKey === 'BVG-2') {
return [
{ key:'Mehringdamm', value:'Mehringdamm' },
{ key:'Alt-Mariendorf', value:'Alt-Mariendorf' }
];
}
return [];
}
The above is a simple example where if BVG-1 is the issue key, two options are returned. If BVG-2 is the issue key, two other options are returned. All other issue keys show no options.
User (Jira Username)
URL parameter to use: {User}
Example URL: https://example.dev/?user={user}
Your script can be configured so that the dynamic dropdown will display different values based on the Jira user for whom time is being logged. In this case, the URL parameter that Tempo sends to the external service contains the Jira username. Following is the script excerpt that highlights this use case:
if (!fieldName && userRaw) {
const isElias = ELIAS_IDS.includes(userRaw);
values = isElias
? [{ key:'Contractor', value:'Contractor' }]
: [{ key:'Internal', value:'Internal' }];
}
The above is a simple example where if Elias is the Jira User, the value chosen is Contractor; all other Jira users are Internal.
Custom Field (Jira Issue)
URL parameter to use: {NameOfJiraCustomField}
Example URL: https://example.dev/?SOW={SOW}
Your external service can be configured so that the dynamic dropdown will display different values depending on any Jira custom field. In this case, the URL parameter that Tempo sends to the external service contains the key of the item that is selected in the custom field on the Jira issue.
Following is the relevant excerpt from the example script, which references the Jira custom field SOW, and shows different values in Timesheets’ Log Time Form depending on the SOW value on the Jira issue.
case 'SOW':
case 'sow':
if (val === 'Software Development') {
return [
{ key:'iOS', value:'iOS' },
{ key:'Android', value:'Android' }
];
} else if (val === 'Consulting Services') {
return [
{ key:'Migration', value:'Migration' },
{ key:'Setup', value:'Setup' }
];
} else if (val === 'Training Services') {
return [
{ key:'Enablement', value:'Enablement' },
{ key:'Documentation', value:'Documentation' }
];
} else if (val === 'Research Analysis') {
return [
{ key:'Customer Interviews', value:'Customer Interviews' },
{ key:'Focus Groups', value:'Focus Groups' }
];
}
return [];
default:
return [];
Account (on the Jira issue)
URL parameter to use: {Account}
Example URL: https://example.dev/?account={Account}
Your script can be configured so that the dynamic dropdown displays different values based on the Tempo Account on the Jira issue. Following is the excerpt for this case:
case 'Account':
case 'account':
if (val === 'Billable') {
return [
{ key:'345340', value:'345340' },
{ key:'345350', value:'345350' }
];
} else {
return [{ key:'000000', value:'000000' }];
}
Tempo Work Attributes
Account (as a Tempo work attribute)
URL parameter to use: {_KeyOfAccountWorkAttribute_}, e.g. {_AccountName_}
Example URL: https://example.dev/?accountname={_AccountName_}
Tempo Account can also be set as a work attribute. Your script can be configured so that the dynamic dropdown displays different values based on the account that the user selects in the Account field (work attribute) in the worklog. Following is an example script for this case:
}
case 'AccountName':
case 'accountname':
if (val === 'CLIENTA') {
return [
{ key:'Canada', value:'Canada' },
{ key:'USA', value:'USA' }
];
} else if (val === 'CLIENTB') {
return [{ key:'APAC', value:'APAC' }];
} else {
return [{ key:'Global', value:'Global' }];
}
References to other dynamic dropdowns (Cascading dynamic work attributes)
URL parameter to use: {_WorkAttributeKey_}, e.g. {_DD1_}, {_DD2_}
Example URLs:
First dynamic dropdown (Level 1):
https://example.dev/
Second dynamic dropdown (Level 2):
https://example.dev/?firstAttr={_DD1_}
Third dynamic dropdown (Level 3):
https://example.dev/?secondAttr={_DD2_}
Your script can be configured so that the dynamic dropdown displays different values based on the value selected from one or more other dynamic dropdown work attributes in a series. This is called a cascading dynamic dropdown. For example, if you had a series of three dynamic dropdowns where the value selected for the first dropdown (work attribute) determined the values displayed for the second, and the value selected for the second dropdown (work attribute) determined the values displayed for the third, below is an example of this 3-level set of cascading dropdowns. Following is the excerpt from the script above that enabled a 3-level cascading dropdown:
For more information on how to set up cascading dynamic work attributes from the Work Attributes settings page in Timesheets, see Cascading Dynamic Work Attributes.
}
function getNextOptions(field, val) {
switch (field) {
case 'firstAttr':
return ({
'Toronto': [{ key:'Development', value:'Development' }],
'Alberta': [{ key:'QA', value:'QA' }],
'Nova Scotia': [{ key:'Design', value:'Design' }]
}[val] || []);
case 'secondAttr':
return ({
'Development': [
{ key:'Data Center', value:'Data Center' },
{ key:'Cloud', value:'Cloud' }
],
'QA': [
{ key:'Performance Testing', value:'Performance Testing' },
{ key:'Security Testing', value:'Security Testing' }
],
'Design': [
{ key:'Graphic Design', value:'Graphic Design' },
{ key:'UX/UI Design', value:'UX/UI Design' }
]
}[val] || []);