QQL (Quevell Query Language) is the language questions about issues are asked in: in search, in saved filters, on boards, and in the conditions of automation rules. It is one language with one parser everywhere — a query that works in search works verbatim in a rule's condition.
If you write JQL, the syntax will feel familiar: =, in, ~, and, order by mean what you expect. What is deliberately absent: history predicates (was, changed). Every QQL operator executes with a predictable cost.
Syntax
A query is a condition and, optionally, an ordering:
status = "In progress" and assignee = currentUser() order by updated desc
- Conditions combine with
andandor, group with parentheses, negate withnot. - Case does not matter:
Status,statusandSTATUSare one field;ANDandandare one word. - Emptiness is asked in words:
dueDate is empty,assignee is not empty. Equality with "nothing" is deliberately inexpressible. - Value lists:
status in ("To do", "In progress"),assignee not in (anna@example.com, ivan@example.com). - An ordering alone is a whole query:
order by updated descmeans "everything I can see, freshest first".
A query always answers within the asker's sight: issues in projects you cannot see do not exist for your query. The same holds for automation rules — their conditions answer through the eyes of the rule's account.
Operators
| Operator | Meaning |
|---|---|
= , != | Equals, does not equal |
in , not in | Is in a list, is not |
~ | Contains text |
is empty , is not empty | Empty, not empty |
> >= < <= | Comparison of numbers, dates and durations |
Not every operator applies to every field: a date has no ~, a text has no >. Such a query is not executed into nothing — it is refused at parse time, pointing at the position and listing the operators the field does accept. The full picture is in the tables below.
Operator precedence
not binds tighter than and, and tighter than or. a or b and c means a or (b and c). Parentheses always win: (a or b) and c.
Quoting and escaping
A value with spaces or special characters goes in quotes, double or single: status = "In progress", summary ~ 'new office'. There is no escaping inside quotes: a string containing double quotes goes in single quotes, and the other way round. Values without spaces are written bare: OFFICE-13, -7d, 2026-08-24, anna@example.com.
Values of enumerated fields resolve from any of their spellings — the key, the default name, and every translation. status = "In progress" and its Russian spelling are the same query: the value becomes the stable key, and the filter survives the status being renamed.
System fields
This table is generated from the product's code and cannot fall behind it.
| Field | Operators | Sorts |
|---|---|---|
assignee | = != in not in is empty is not empty | no |
created | > >= < <= | yes |
description | = != ~ is empty is not empty | no |
dueDate | is empty is not empty > >= < <= | yes |
key | = != in not in | yes |
level | = != in not in | no |
originalEstimate | = != is empty is not empty > >= < <= | yes |
parent | = != in not in is empty is not empty | no |
priority | = != in not in is empty is not empty | yes |
project | = != in not in | yes |
remainingEstimate | = != is empty is not empty > >= < <= | yes |
reporter | = != in not in is empty is not empty | no |
startDate | is empty is not empty > >= < <= | yes |
status | = != in not in | no |
statusCategory | = != in not in | no |
summary | = != ~ is empty is not empty | no |
team | = != in not in is empty is not empty | no |
text | ~ | no |
timeSpent | = != > >= < <= | yes |
type | = != in not in | no |
updated | > >= < <= | yes |
Notes on individual fields:
keyandparenttake issue keys:key in (OFFICE-1, OFFICE-2),parent = OFFICE-13. Key case does not matter.textis what the plain search box searches: summary and description together.~only.teamasks whether a team is marked on the issue:team = "Logistics". One issue can carry several teams, so equality here means containment.statusCategorytakes three values —TODO,IN_PROGRESS,DONE— and the spellingsto doandin progress.prioritytakeshighest,high,medium,low,lowest.- Durations (
timeSpent,originalEstimate,remainingEstimate) compare in the time-tracking form:timeSpent > 1d,originalEstimate <= 2w, where1w= 5 days,1d= 8 hours. startDateanddueDateare calendar days: "due this week" is the same question in every timezone.createdandupdatedare instants.
Custom fields
A custom field is asked by its name: Budget > 100000, "Launch date" < startOfMonth(). Its operators come from its type:
| Field type | Operators | Sorts |
|---|---|---|
| Date | = != is empty is not empty > >= < <= | yes |
| Issue | = != in not in is empty is not empty | no |
| Multi-select | in not in is empty is not empty | no |
| Number | = != is empty is not empty > >= < <= | yes |
| Paragraph | ~ is empty is not empty | no |
| Select | = != in not in is empty is not empty | yes |
| Text | = != ~ is empty is not empty | no |
| URL | = != ~ is empty is not empty | no |
| User | = != in not in is empty is not empty | no |
- A number has no
~— a substring of a number says nothing about it. A paragraph has no=— exact equality with a page of text is a question nobody has ever meant to ask. A multi-select is asked only withinandnot in. - Fields are defined per project, so one name can mean several definitions. A query by name means all of them, as long as they share a type; a name that means a date in one project and a paragraph in another behaves as text.
- A query stores stable identifiers, not names: renaming a field or an option does not break a saved filter.
Functions
| Function | What it means |
|---|---|
currentUser() | Whoever runs the query. In a saved filter: whoever opened it, not whoever wrote it. |
myReports() | My direct reports by the organization chart. Not including me. |
myVertical() | Everybody under me in the tree, any depth. Not including me. |
myTeammates() | Everybody in every team I am in, whatever the role. Not including me. |
now() | The current moment. |
startOfDay() , startOfWeek() , startOfMonth() | The start of today, of this week (Monday), of this month. |
myReports(), myVertical() and myTeammates() name several people, so they are asked with in: assignee in (myReports()). The form assignee = myReports() is refused with a hint. "My team and me" is composed: assignee in (myReports(), currentUser()).
People and time functions resolve on every run, not at save time: a filter for "updated in the last week" means the reader's last week every time it is opened.
Dates and relative time
Date and moment fields compare against:
- a fixed date:
duedate <= 2026-09-15; - a relative offset:
updated > -7d(seven days ago),duedate <= +7d(seven days ahead); unitsd,w,m— days, weeks, months; - functions:
created >= startOfMonth(),updated > startOfWeek().
People
A person is named by display name or email: assignee = "Anna Petrova", assignee = anna@example.com. When two people share a name, a query by that name is refused and asks for the email: two people with one name is a fact about the company, and the query has to say which one it meant.
Sorting
order by field plus a direction: asc (the default) or desc. Whether a field sorts is in the tables above; ordering by an unsortable field is refused at parse time. One sort key per query.
project = OFFICE and status != Done order by duedate asc
Error messages
A parse error points at a position in the query and says what is wrong. The common messages and what they mean:
| Message | What it means |
|---|---|
There is no field called "..." | No such field. Check the name; custom fields are asked by field name, statuses are values of status. |
"..." does not support ~. It accepts =, !=, ... | The operator does not apply to this field; the list names the ones that do. |
There is no status called "..." | The value is not in the field's vocabulary. A short vocabulary is quoted right there. |
Nobody here is called "..." | No person with that name or address. |
2 people are called "...". Use their email address instead | The name is ambiguous; ask by email. |
"..." is not a date. Use 2026-08-24, -7d, startOfWeek() or now() | The value does not read as a date or an offset. |
That is not a duration. Use the 1w 2d 4h 30m form (1w = 5d, 1d = 8h) | The duration is written in another form. |
"..." is not an issue key like PROJECT-12 | The field expects an issue key. |
"..." cannot be ordered by | The field does not sort. |
This quote is never closed | A quote is opened and never closed. |
myReports() names several people. Ask with "assignee in (myReports())" | The function names several people and is asked with in. |
Examples
assignee = currentUser() and status != Done order by duedate asc
My open issues, nearest deadlines first.
project in (OFFICE, WAREHOUSE) and priority in (highest, high) and status = "In progress"
What is burning, in progress, across two projects.
assignee in (myVertical()) and duedate < startOfWeek() and status != Done
Overdue work anywhere under me in the tree.
summary ~ "office move" or description ~ "office move"
Issues mentioning the office move in the summary or the description. The same, shorter: text ~ "office move".
type = Bug and created >= -30d and assignee is empty
A month of bugs that nobody owns.
"Budget" > 500000 and "Launch date" <= startOfMonth()
Custom fields: a number and a date.
team = "Logistics" and remainingEstimate > 1w
The team's issues with more than a week of work left.