The JQL function module (for Forge or Connect) creates a new custom JQL function, which appears built-in from the user’s perspective - it’s visible in the editor and shows up in the autocomplete dropdown. However, the function’s business logic is executed within the app codebase. Therefore, it’s important to have a basic understanding of how custom JQL functions work in Jira.
The point of custom functions contained in JQL queries is to be replaced by native JQL. Apps tell Jira how to replace a specific function into a valid JQL fragment.
These are the stops involved in executing a JQL search in Jira:
Calling an app to evaluate a custom function may be slow. Therefore, instead of doing it on every JQL search, Jira evaluates each custom function once and saves the result to the database. A stored mapping between a custom function and native JQL is called a precomputation.
Note that this function-to-JQL mapping is aware of the function arguments. One function can have multiple precomputations, one for each set of arguments the function was called with.
It is the App's responsibility to return a valid JQL string to be inserted in the original JQL query in place of the JQL function.
The App should keep precomputations up to date. For this purpose, we're exposing a Precomputation API to browse stored precomputations and update them if needed. Each precomputation is returned with its metadata, including time of last usage, which can be used by the App to decide whether a precomputation should be removed.
Precomputations not evaluated for 7 days are removed automatically by Jira. Once a function that had its precomputation deleted is encountered again in a JQL search, the app will be invoked to compute a fresh result and a new precomputation will be stored.
The best approach is to update precomputations as a reaction to a related product event/webhook, for example when a new issue is created. Please refer to our Forge example app to see some patterns and strategies to optimize precomputation updates.
Precomputations are not scoped to users, only to the function and its argument set. This reduces the load on both the app and Jira, as the app doesn't need to keep track of precomputations for each user separately.
Note that the final result of a query will still be affected by permissions. A user will never receive issues they don't have permission to see. This means that you don't need to worry about permissions if your app returns a set of issues; just return all issues that satisfy the function clause, and let Jira apply user permissions to the final result.
The app will be called whenever the function needs to be evaluated. The app is expected to return a JQL fragment that can be inserted into the JQL query in place of the function.
The argument that functionKey
will receive looks like this:
1 2{ "precomputationId": "<uuid>" "clause": { "field": "key", "type": [ "issue" ], "operator": "in", "functionName": "issuesWithText", "arguments": [ "Test" ] } }
The above input may correspond to the following function found within a JQL query: issue in issuesWithText("Test")
.
field
is the canonical name of the field in the left-hand side of the function clause.
Notice that its value in the example is key
, and not issue
. This is because some fields can have alternative names in JQL.
This particular field can be referred to as key
, issue
, or issuekey
, but the canonical name is key
, and this is what's sent to the app,
no matter which of the alternatives was used in the original query.
type
is a list of value types that the field expects in the right-hand side of the function clause.
All fields in JQL have a specific type, and different fields can share the same type.
For example, type user
is shared by fields like assignee
, reporter
, or creator
, among others.
Functions are scoped to types, not fields, so they're applicable to all fields that share a type. If you want your function to work only with specific fields, you can always return an appropriate error during evaluation when your function receives a request for a field that it doesn't support.
precomputationId
is the ID of the precomputation that will be created for the function after its first evaluation finishes.
The app can save it for later use, for example to update the precomputation when the function results change.
The function should return either
1 2{ "error": "Error message returned by app." }
1 2{ "jql": "id in (1, 2, 3)" }
Note: the fragment can be any valid JQL, not just something of the form id in (1, 2, 3)
. An equally valid response could be:
1 2{ "jql": "summary ~ someText" }
In case an error is returned, an additional boolean field storeErrorAsPrecomputation
can be returned:
1 2{ "error": "this is an error", "storeErrorAsPrecomputation": true }
The default value if missing is false
. If set to true
, then the error will be stored as a precomputation with the normal TTL of 7 days.
Note again that the JQL fragment returned by the app isn't a complete JQL query. It’s just a fragment that'll be inserted into the original query in place of the function.
Our example function clause issue in issuesWithText("Test")
could be a part of a longer query, for instance:
1 2issue in issuesWithText("Test") and assignee is not empty order by created
The final query that will be sent to the JQL engine for final evaluation is equal to the original query, with the function clause replaced by the returned fragment:
1 2id in (1, 2, 3) and assignee is not empty order by created
To improve the performance of functions that return a list of issues, enumerate their IDs in your JQL query using the id
field: id in (1, 2, 3...)
.
While using issue keys may feel more natural, we recommend enumerating by IDs because our internal systems are optimised for it.
There is a limit of 1,000 right-hand side values in the JQL fragment returned by custom functions.
Forge-provided functions have a time limit of 25 seconds, while Connect-provided functions have a time limit of 50 seconds.
If your app defines a function whose name matches that of a built-in JQL function, it won’t work.
There are no guarantees on which function is invoked by Jira in the rare case of multiple apps defining JQL functions with the same name.
Here are a few example Forge and Connect apps to help you learn about the JQL function module:
Rate this page: