TQL query language
TQL, the TOW Query Language, filters tickets with readable expressions like:
status = open AND label = backend ORDER BY priority ASC
TQL is designed for saved ticket views, board definitions, and advanced ticket filtering. It is intentionally similar to Jira JQL, but it uses TOW ticket fields, project workflows, and project custom fields.
Basic shape
A TQL query has an optional filter expression and optional ordering:
expression ORDER BY field ASC
Examples:
status = open
type IN (bug, story) AND priority <= 2 ORDER BY due DESC
(summary ~ "billing" OR description ~ "billing") AND NOT status = done
project = APP AND cf.customer_tier = enterprise
Boolean logic
Use AND, OR, NOT, and parentheses to combine filters.
TQL applies precedence in this order:
NOTANDOR
When in doubt, use parentheses:
(status = blocked OR priority = 1) AND assignee = currentUser()
Operators
| Operator | Meaning | Example |
|---|---|---|
= | Equals | status = blocked |
!= | Does not equal | type != epic |
< | Less than | priority < 3 |
<= | Less than or equal | due <= 2026-06-30 |
> | Greater than | estimate > 3 |
>= | Greater than or equal | progress >= 50 |
IN (...) | Equals one of several values | type IN (bug, story) |
NOT IN (...) | Does not equal any listed value | status NOT IN (done, archived) |
IS EMPTY | Has no value | assignee IS EMPTY |
IS NOT EMPTY | Has a value | due IS NOT EMPTY |
~ | Text contains | summary ~ "checkout" |
!~ | Text does not contain | description !~ "deprecated" |
Range operators only work on numeric, date, and datetime fields. Text fields support equality, IN, ~, !~, and empty checks where the field can be empty.
Values
TQL accepts:
- Bare values:
open,APP,backend - Quoted strings:
"launch plan",'customer pilot' - Numbers:
1,3.5 - ISO dates:
2026-06-30 - ISO datetimes:
2026-06-30T12:30:00Z - Functions:
currentUser(),today(),now() - Relative dates:
-7d,+2w,-1m,+1y
Use quoted strings when a value contains spaces, punctuation, or words that could be confused with TQL keywords:
summary ~ "launch plan"
Relative date units are:
| Unit | Meaning |
|---|---|
h | Hours |
d | Days |
w | Weeks |
m | 30-day months |
y | 365-day years |
Built-in fields
| Field | Aliases | Common operators | Notes |
|---|---|---|---|
issue_key | key, issue | =, !=, IN, ~ | Jira-style ticket key, such as TOW-12 or APP-4. |
issue_number | =, IN, ranges | Numeric part of the issue key. | |
project | =, !=, IN, IS EMPTY | Project key or project ID. | |
issue_type | type | =, !=, IN | task, bug, story, epic, subtask. |
status | =, !=, IN | Project workflow status. status = open means active work. | |
status_category | =, !=, IN | todo, in_progress, or done. | |
title | summary | =, !=, IN, ~, !~ | Ticket title. |
description | =, !=, IN, ~, !~, IS EMPTY | Ticket description. | |
priority | =, IN, ranges | Priority is numeric, where lower numbers are more urgent. | |
start_date | =, IN, ranges, IS EMPTY | ISO date. | |
due_date | due | =, IN, ranges, IS EMPTY | ISO date. |
created_at | created | =, IN, ranges | ISO datetime. |
updated_at | updated | =, IN, ranges | ISO datetime. |
labels | label | =, IN, ~, !~, IS EMPTY | Matches ticket labels. |
assignee | =, IN, IS EMPTY | User ID, me, currentUser(), or unassigned. | |
parent | =, IN, IS EMPTY | Parent ticket issue key or ID. | |
progress_percent | progress | =, IN, ranges | Numeric progress from 0 to 100. |
estimate_points | estimate | =, IN, ranges, IS EMPTY | Numeric estimate. |
rank | =, IN, ranges | Ticket rank used for ordering. |
Status and open work
status filters match workflow status keys. For projects with custom workflows, use the status key configured in project settings:
status = working
status = open is a shortcut for active work. It matches tickets in active status categories, such as todo or in progress.
Use status_category when you want a workflow-independent filter:
status_category = in_progress
Assignees
Use currentUser() or me for tickets assigned to you:
assignee = currentUser()
Use unassigned or IS EMPTY for tickets without an assignee:
assignee = unassigned
assignee IS EMPTY
Labels
Labels are normalized before matching. These examples find tickets with a backend label:
label = backend
labels IN (backend, infrastructure)
Use ~ for partial label text:
labels ~ infra
Custom fields
Project custom fields use the cf. prefix:
cf.customer_tier = enterprise
Because custom fields are defined per project, TQL can only use cf.<key> when there is a single project scope. Provide that scope with a project filter:
project = APP AND cf.customer_tier = enterprise
Or run the query inside a project-scoped ticket view.
Custom field operators depend on the field type:
| Custom field type | Supported filters |
|---|---|
| Short text, paragraph | =, !=, IN, NOT IN, ~, !~, IS EMPTY |
| Number | =, !=, IN, NOT IN, ranges, IS EMPTY |
| Date, datetime | =, !=, IN, NOT IN, ranges, IS EMPTY |
| Single select, multi select, checkboxes | =, !=, IN, NOT IN, IS EMPTY |
| User single, user multi | =, !=, IN, NOT IN, IS EMPTY |
| Labels | =, !=, IN, NOT IN, ~, !~, IS EMPTY |
Examples:
project = APP AND cf.budget >= 10000
project = APP AND cf.launch_date < today()
project = APP AND cf.regions IN (us, emea)
project = APP AND cf.reviewer = currentUser()
Ordering
Add ORDER BY to control result order:
status = open ORDER BY priority ASC, updated DESC
If no direction is provided, TQL sorts ascending:
status = open ORDER BY due
Ordering supports built-in sortable fields, such as priority, due, updated, created, rank, summary, status, and issue_number.
Custom-field ordering is not supported in TQL v1.
Limits and safety
TOW validates TQL before running it. Invalid or overly complex queries return a validation error instead of running.
Current safety limits include:
- Maximum query length: 10,000 characters
- Maximum tokens: 500
- Maximum nesting depth: 64
- Maximum
INlist size: 100 values - Maximum total values: 200
- Maximum predicates: 80
- Maximum text-search predicates: 25
- Maximum custom-field predicates: 25
- Maximum
ORDER BYfields: 10
TQL values are handled as query parameters. User-entered strings are not executed as SQL.
Common examples
Open work assigned to you:
status = open AND assignee = currentUser()
High-priority blocked work:
status = blocked OR (priority = 1 AND status = open)
Tickets due in the next week:
due >= today() AND due <= +7d
Recent work that mentions a customer:
summary ~ "Acme" OR description ~ "Acme"
Open bugs in a project:
project = APP AND type = bug AND status = open ORDER BY priority ASC
Enterprise customer work with no reviewer:
project = APP AND cf.customer_tier = enterprise AND cf.reviewer IS EMPTY
Troubleshooting
If a query returns a validation error:
- Check that field names are spelled correctly.
- Quote values that contain spaces or punctuation.
- Add
project = KEYbefore usingcf.<key>custom fields. - Use range operators only with number, date, or datetime fields.
- Use
ORDER BYonly with built-in sortable fields.
If a query returns fewer tickets than expected, check your project scope and permissions. TQL only returns tickets you are allowed to see.