Rate this page:

Custom JQL functions

The module 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.

High-level overview

  1. Whenever a JQL query with custom functions is sent for evaluation, the first step is to replace them with native JQL.
  2. Function clause is extracted from the query and the business logic of the app is invoked to execute business logic of the function.
    • App returns a JQL fragment as a result of the function processing.
    • Function clause in original query is replaced by JQL fragment returned by the app.
  3. The query is sent to the JQL engine to be evaluated.

JQL functions processing


Calling an app to evaluate a custom function may be slow. Therefore, instead of doing it on every call, we only evaluate each custom function once and save the result to the database. A stored mapping between a custom function call and native JQL is called a precomputation. To limit the number of stored precomputations which must be updated by apps, they’re deleted from the database after their TTL of one week expires.

App’s responsibilities

App must define the business logic for the custom function that will be invoked by Jira. The custom function should return a valid JQL fragment that can later replace the function clause in the original query.

App should keep precomputations up to date. For this purpose, we're exposing the new Precomputation API to browse stored precomputations and update them if needed. Each precomputation will contain metadata, such as the last time the function was used, which you can use to guide update logic.

When to update precomputations?

Function evaluation

The app will be called whenever the function needs to be evaluated. It's expected to return a JQL fragment that the function clause will be replaced with.


The argument that functionKey will receive looks like this:

  "precomputationId": "<uuid>"
  "clause": {
    "field": "key",
    "type": [
    "operator": "in",
    "functionName": "issuesWithText",
    "arguments": [

Note that type is the type of the field the function is executed against. In this case it’s the same as the field name, which may be confusing. However, imagine a function that works with users - we’d have field: assignee and type: user.

precomputationId is also sent as an input argument. It allows the app to store this UUID internally, which means it doesn’t have to fetch precomputations just to get the correct ID.


The response that the function is supposed to return can be either

  • an error message, or
  "error": "Error message returned by app."
  • a JSON with a JQL fragment that the function clause will be replaced with in the original query.
  "jql": "id in (1, 2, 3)"

To improve the performance of functions that return a list of issues, enumerate their IDs in your JQL query like id in (1, 2, 3...). While using keys may feel more natural, we recommend enumerating by IDs because it can make the operation faster.

There is a limit of 1,000 right-hand side values in the JQL fragment returned by custom functions.

Name collisions

If your app defines a function whose name matches that of a built-in JQL function, it won’t work.

Also keep in mind that during JQL search processing, built-in functions always have higher priority than app-defined ones.

If multiple apps define a JQL function with the same name, the one invoked during query processing is picked randomly.

Example apps

Here are a few example Forge and Connect apps to help you learn about the JQL function module:

Rate this page: