Formulas
A formula column computes a value for each row from that row's fields and from the rows around it. Add one with Formula… in Add a column, and change it later with Edit formula… in its header menu.
On this page
The formula dialog
| Field | What it holds |
|---|---|
| Column name | The name shown on the header, such as "Remaining points". |
| Formula | The expression. Problems show as you type, pointing to the character where they occur. |
| Fields for the names in the formula | The Jira field each name reads. A name that matches a field's id or name is filled in for you: story_points finds Story Points. For other names, you pick the field from a list. |
| Show values as | Automatic, Number, Percent (0 to 1 shown as %), Duration (seconds shown as 1d 2h), Date, Text, or Markup (*bold*, _italic_ and [label|https://…] links). |
The app checks the formula again when you save it, and refuses one that can't be parsed, calls an unknown function, or uses a name with no field.
The language
A formula is an expression. Names and functions are not case sensitive.
story_points * 2
IF(status = "Done", 1, 0)
summary.lower().left(10)
- Numbers, text in double or single quotes (with
\to escape),trueandfalse, and lists in square brackets. - Arithmetic with
+ - * /, and comparisons with= != < <= > >=. AND,ORandNOT.- Comments with
//to the end of the line, or between/*and*/. - Chained calls:
summary.lower()is the same aslower(summary).
Local names and functions
WITH gives a value a name for the rest of the formula, and can define a function of your own:
WITH rest = timeestimate : IF(rest > 0, timespent / (timespent + rest), undefined)
WITH half(x) = x / 2 : half(SUM{story_points})
Missing values and errors
A missing value makes arithmetic missing, so a sum with an empty field stays empty. Use IF_DEFINED or DEFAULT to supply a value. An error, such as a division by zero or a bad argument, passes through everything except IF_ERROR.
Names you can use
key and depth come from the row (the top level is depth 1). These names read the issue's fields without any setup: summary, status, status_category (To Do, In Progress or Done), type or issuetype, assignee, reporter, priority, resolution, created, updated, duedate, resolutiondate, timespent, timeestimate, timeoriginalestimate, labels, components, fixversions and project. Time fields are in seconds. You bind any other name to a field in the dialog.
User fields hold users, which show and compare by name. Two users are the same person when their account ids match. me() is the person viewing the page.
Reading other rows
Aggregations read the values of other rows. Write the function, any options after #, and the expression in braces:
SUM{story_points}
SUM#leaves{story_points}
JOIN#separator=" / "{key}
PERCENTILE#p=0.9{story_points}
SUM#preceding{story_points} + story_points
The functions are SUM, AVG, MIN, MAX, COUNT, JOIN, MEDIAN, UMIN, UMAX, PERCENTILE and QUARTILE.
By default an aggregation reads every row below. These options change which rows it reads and how:
| Option | Effect |
|---|---|
#subtree |
The row itself and every row below it. |
#children |
Only the rows directly below. |
#leaves |
Only the rows at the bottom of the sub-tree. |
#ancestors |
The rows above it. |
#preceding |
The row's earlier siblings, for running totals. |
#level=2..3 |
Only rows at those levels. The top level is 1. |
#depth=1..2 |
Only rows that many levels below the row. Its children are 1. |
#distinct |
Each issue once, however many rows show it. |
#separator="…" |
The separator for JOIN. The default is a comma. |
#ordering=asc or desc |
Sorts the values first. JOIN lists them in that order. |
#compact |
Leaves out empty text as well as missing values. |
#strict |
The result is missing when any value is missing. |
#p=0.9 |
Which percentile, from 0 to 1. The default is 0.5. |
#q=1 |
Which quartile, from 0 to 4. The default is 2. |
A total still waiting for issues below it is marked incomplete until they load.
Queries in formulas
JQL{status = Done} is true when the row's issue matches the JQL query, with Jira answering as you. TQL{leaves} is true when the row matches the tree query. Put the query between the braces as is. A row shows "…" until its answer arrives, and a query Jira refuses shows Jira's reason.
IF(JQL{status = Done}, 1, 0)
COUNT{TQL{leaves}}
Functions
| Group | Functions |
|---|---|
| Arithmetic | abs ceiling floor round sign sqrt pow exp ln log log10 mod |
| Logic | if case choose and or not if_defined if_error is_error default |
| Text | concat left right mid substring len lower upper trim replace replace_at repeat search match split join index_of last_index_of string number |
| Lists | array map filter reduce sort sort_by reverse unique flatten merge_arrays subarray first last size contains contains_any contains_all is_empty sequence |
| Statistics | sum avg average min max umin umax median percentile quartile stdev stdevp count |
| Dates and times | now today date datetime make_date make_datetime date_add date_subtract date_set datetime_add datetime_subtract days_between hours_between months_between years_between weekday weeknum day month year hour minute second start_of_month end_of_month truncate_time truncate_to_day truncate_to_hour truncate_to_minute truncate_to_week truncate_to_month truncate_to_year format_datetime parse_datetime |
| Working days | workdays_between add_workdays is_workday |
| Durations | duration format_duration calendar_seconds calendar_minutes calendar_hours calendar_days calendar_weeks jira_minutes jira_hours jira_days jira_weeks |
| Users | me user_display_name user_email user_name user_is_active |
| Other | url_encode url_decode historical_value hierarchy_icon_url |
Notes on some of them:
map(list, $ * 2)andfilter(list, $ > 3)use$for each item.reduce(list, 0, $acc + $)also has$acc, the value so far.sort_by(labels, len($), "desc")sorts by a key worked out for each item, and keeps the order of equal items.sequence(1, 10)makes a list of numbers, with an optional step. The list can hold up to 10,000 numbers.historical_value(status, "2026-10-01")is the value a field had at a moment, read from the issue's history. If the field hasn't changed since, you get the current value. For a moment before the issue existed, you get an empty value.hierarchy_icon_url("issuetype")gives the address of the row's issue type icon."priority"and"assignee"give the priority icon and the assignee's picture.user_emailreturns an address only when Jira shows it to you.user_nameis the account id, since Jira Cloud has no user names.- The working-day functions and the
jira_durations follow the site's working calendar: days off are skipped, and a day or a week is as long as the calendar says (8 hours and 5 days unless an admin changed it). Thecalendar_durations use clock time.
Examples
| Goal | Formula |
|---|---|
| Remaining story points below a row | SUM{IF(status_category != "Done", story_points, 0)} |
| Share of leaves that are done | COUNT#leaves{IF(status_category = "Done", 1, undefined)} / COUNT#leaves{1} |
| Days until the due date | days_between(today(), duedate) |
| Keys of the children | JOIN#children#separator=", "{key} |
| Running total of estimates | SUM#preceding{timeoriginalestimate} + timeoriginalestimate |
Performance
Your browser works out formulas over the rows of the view, using the issue data the grid already has. With 2,000 rows or more, the work runs in the background so scrolling and typing stay quick. A very heavy formula stops with an error value instead of freezing the page.
In exports, formula values keep their kind: numbers, percentages, durations and dates stay numbers and dates in a spreadsheet.
Questions about the app? Write to info@plugincraft.ca.