Query Language

TQL — the Trakr Query Language

One syntax for every surface: search, lists, boards, roadmaps, calendars, the Support Desk queue, reports, automation and dashboard widgets. History operators, relative date maths, positional validation — and an AI mode that hands you back a query, never a black box.

What you get

One parser, everywhere, with a safety net

  • The same syntax on every surface, including reports and automation
  • Live autocomplete on field names and on values
  • Errors carry a character position and a suggestion
  • Historical operators on status, assignee, priority, resolution, type
  • Relative date maths and duration notation built into the grammar
  • Saved filters — personal, shared org-wide, favourite
Shared ground

Ideas TQL did not invent

  • JQL has had WAS, WAS IN and CHANGED for years
  • Boolean grouping, IN / NOT IN, IS EMPTY, ORDER BY
  • A currentUser()-style function for personal queries
  • Relative dates as a concept are common to both
Honest limits

Deliberately bounded

  • The field list is closed — TQL queries Trakr's real fields
  • No fixVersion: Trakr has no project versions by design
  • History operators cover five fields, not every field
  • A half-bounded BETWEEN is refused, not guessed
  • currentUser() is refused in an automation rule's filter

Shape of a query

A query is a sequence of field operator value clauses joined by AND or OR, optionally grouped with parentheses, optionally negated with NOT, and optionally sorted.

field operator value [AND | OR] … [ORDER BY field [ASC | DESC]]

Queries you would actually write

# my open work, most urgent first
project = OPS AND statusCategory != DONE AND assignee = currentUser()
  ORDER BY priority DESC, updated DESC

# anything that went through review and closed last quarter
status WAS "In Review" AND resolution = DONE AND resolved > -1q

# tickets that were handed off — who did Luc give away?
assignee CHANGED FROM "luc.dupont" AND updated BETWEEN -14d AND now()

# unassigned and due before the week is out
project = SUP AND assignee IS EMPTY AND due < endOfWeek()

# full-text across summary and description
text ~ "certificate expired" AND created > -7d

# service-linked work with real time on it
service = "Payments" AND label IN (regression, hotfix) AND timeSpent > 2h

# everything that blew a first-response target
firstResponseBreached = true AND slaDue < now() ORDER BY slaDue ASC

# the backlog in the order the team actually ranked it
project = OPS AND statusCategory = TODO ORDER BY rank ASC
01 / Core

The ticket itself

project · key (wildcards) · type · status · statusCategory · priority · resolution · summary · description · feature · requestKind · incidentFlag

requestKind is what makes service requests, incidents, problems and changes queryable side by side — they are all tickets, not separate record types.

02 / People

Who is involved

assignee · reporter · watcher

All three accept currentUser(), a username, or a list. Negation reaches empty rows: assignee != ana returns unassigned tickets too, which is almost always what you meant.

03 / Dates

When things happened

created · updated · due · startDate · resolved

Every date field takes an absolute date, a date function, or relative date maths. A bare date means the whole day, not midnight — created = 2026-08-28 does what you expect.

04 / Collections

Many-to-many attachments

component · service · label · parent

service is the ITSM services catalogue; service IS EMPTY finds work nobody has mapped to a service yet.

05 / Time

Estimates and effort

originalEstimate · timeSpent

Both take duration notation — 30m, 2h, 1d — where a day is eight hours, the same conversion the timesheets and budget reports use.

06 / Links

Dependencies

linkedTo · blocks · blockedBy

Query the dependency graph without opening it: blockedBy IS NOT EMPTY AND statusCategory = IN_PROGRESS is a standing list of work that cannot actually move.

07 / Full text

One field for prose

text

Backed by a GIN full-text index over summary and description. Use ~ to match and !~ to exclude; summary and description remain separately queryable when you need to be precise.

08 / SLA

Promises and breaches

slaStatus · slaDue · firstResponseBreached · resolutionBreached

SLA state is a first-class query target, not a report you have to run. See SLA management for how those values are produced.

Operator
Works on
Notes
= !=
Every field
Negation reaches empty rows — assignee != ana includes unassigned tickets.
> >= < <=
Dates, time, priority
Ordering on priority uses your organisation's own priority rank, not alphabetical order.
BETWEEN … AND …
Dates, time
Both bounds are required. A half-bounded range is refused rather than silently interpreted.
~ !~
Text fields
Contains / does not contain. On text this runs against the full-text index.
IN NOT IN
Option, people, collections
Parenthesised list — label IN (regression, hotfix).
IS EMPTY IS NOT EMPTY
Every nullable field
The honest way to ask for missing data — no sentinel values, no = null.
WAS WAS NOT
History
Did this field ever hold this value? Activity-log backed, on status, assignee, priority, resolution and type.
CHANGED
History
Did this field move at all? Combine with a date clause to bound the window.
CHANGED FROM CHANGED TO
History
Directional transitions — reassignment chains, reopens, priority escalations, all without a report.
AND OR NOT ( )
Structure
Grouping is explicit. Unbalanced parentheses are a validation error with a position, not a guess.
Note Historical operators are supported on status, assignee, priority, resolution and type Using one on any other field is a validation error, not an empty result
Token
Kind
Meaning
currentUser()
People
The signed-in account. Makes one saved filter work for the whole team. Refused inside an automation rule's filter, where "current user" has no meaning.
now() today()
Date
The instant, and the current day.
startOfDay() endOfDay()
Date
Day boundaries, for when you need an explicit edge rather than the whole-day default.
startOfWeek() endOfWeek()
Date
Week boundaries — the backbone of "due this week" widgets.
startOfMonth() endOfMonth()
Date
Month boundaries, for billing and reporting windows.
startOfQuarter() endOfQuarter()
Date
Quarter boundaries.
startOfYear() endOfYear()
Date
Year boundaries.
-7d +3d -2w
Relative
Days and weeks, offset from now. The sign is required — there is no ambiguous bare 7d.
-1m -1q -1y
Relative
Months, quarters and years. resolved > -1q is a rolling quarter that never needs maintenance.
30m 2h 1d
Duration
For originalEstimate and timeSpent. One day is eight hours, matching the working-day maths used in timesheets and budgets.
01 / Status

Names first, category as a fallback

status matches workflow status names first, and only falls back to matching a category when no status carries that name. So status = "In Review" means the status your workflow actually defines, while status = DONE still works when nobody named a status "Done".

When you specifically want the category — across projects with different workflows — ask for it: statusCategory = IN_PROGRESS.

02 / Priority

Your rank, not the alphabet

Comparisons like priority >= High use your organisation's own priority ordering — the position you assigned in settings. If you renamed the set, added a fifth level or reordered them, TQL follows.

This also drives ORDER BY priority DESC, so the top of the list is the top of your scale.

03 / Dates

A bare date means the whole day

created = 2026-08-28 matches everything filed that day, not the single instant of midnight. It is the reading a human intends, and it removes the classic off-by-one-day bug from every saved filter.

When you want the edge instead, startOfDay() and endOfDay() are there.

04 / Negation

Negations reach empty rows

assignee != ana returns unassigned tickets. In SQL terms that is a deliberate departure from three-valued logic, because "not Ana" in an operations review always means "including the ones nobody has picked up".

The same applies to service, component and the rest of the collection fields.

05 / Ranges

A half-bounded BETWEEN is refused

Writing created BETWEEN -30d and nothing else does not silently become an open-ended range. It is a validation error naming the missing bound. Guessing here produces a plausible, wrong answer — which is worse than no answer.

06 / Sorting

Including the order you dragged

Sort by created, updated, due, startDate, resolved, priority, status, type, key, summary, assignee, reporter, cycle — and by rank, the project's own hand-ordered backlog position.

Every sort is tie-broken by key, so pagination is stable and page two never repeats a row from page one.

What the parser refuses, and how it tells you

Validation returns an error with a position in the query string, so the editor can put the caret where the problem is. The cases it names:

  1. Unknown field — refused with a suggestion of the closest real field name.
  2. Unbalanced parentheses — refused with the position of the offending bracket.
  3. Unclosed quote — refused rather than swallowing the rest of the query as a string.
  4. Operator invalid for the field type — a text operator on a date, an ordering operator on a set.
  5. Historical operator on an unsupported field — named explicitly, so you know it is the field and not the syntax.
  6. Missing value — a clause with an operator and no operand is an error, never an implicit "anything".

Autocomplete works against the same metadata the validator uses — field names and their values — so most of these never reach the point of being submitted.

Surface
What TQL does there
Global search bar
Autocomplete, recent searches, a saved-filter dropdown and a / keyboard shortcut from anywhere in the product.
Advanced search
A full page with a scope selector — All, Projects, or Support Desk — because the three worlds never leak into each other.
Ticket list & board
The same bar filters the list and every board column, project-level and cross-project.
Roadmap & calendar
Both the per-project and cross-project roadmap are TQL-filterable, as is the calendar.
Support Desk queue
The agent queue carries the full TQL bar alongside the column manager, grouping and bulk actions. See Service Desk.
Service Management
Incidents, problems and changes are tickets, so requestKind = PROBLEM is an ordinary clause. See ITSM.
Report generator
Ad-hoc reports are built from a TQL query, saved with their definition, and scheduled for delivery. See Reporting.
Automation rules
A rule selects its tickets with a TQL filter. See Automation.
Dashboard widgets
The SAVED_FILTER widget renders any saved query as a table with its own column manager and CSV export.

Saved filters

A query you had to think about should only be written once.

  • Personal Yours alone
  • Shared Visible organisation-wide
  • Favourite Pinned to your search dropdown

Saved filters are the connective tissue: the same definition drives a dashboard widget, an automation rule's selection, and a scheduled report.

The badge toggles

The TQL badge on the search bar switches to AI. You ask a question in your own words; Trakr returns generated TQL — never results. The query appears under the bar, editable as ordinary TQL, and it is run through the same parser and validated before it is handed back. If the model produces something the parser refuses, you never see it.

This is the whole design argument. A natural-language search that returns rows asks you to trust an answer you cannot inspect. Returning the query means you can read it, correct it, save it, and re-run it tomorrow with identical semantics.

you type ›  high priority bugs Luc picked up this month that are still open

trakr returns ›
type = Bug AND priority >= High AND assignee = "luc.dupont"
  AND statusCategory != DONE AND created >= startOfMonth()

The people resolver

Models are bad at usernames and good at names. A TqlPeopleResolver injects your organisation's own directory into the prompt in the form luc.dupont (Luc Vermeulen), then rewrites a loose assignee ~ "Luc" into an exact assignee = "luc.dupont".

  • Ambiguous names Left alone, never guessed
  • Customer accounts Excluded from the directory
  • currentUser() and lists Never rewritten
  • Directory cap 200 accounts

Past 200 accounts the roster is omitted rather than truncated — a half-directory would make the model confidently rewrite the wrong person, and a resolver that is sometimes wrong is worse than one that is sometimes absent.

AI features run against whichever provider your organisation configured, including self-hosted and OpenAI-compatible endpoints. See AI in Trakr.

Is TQL just a copy of JQL?

No, and the honest comparison matters. JQL has had history operators for years — WAS, WAS IN, WAS NOT and CHANGED with its modifiers are long-standing Jira features, and anyone telling you otherwise is wrong.

What TQL offers is one syntax that behaves identically on every surface in Trakr — global search, ticket list, board, roadmap, calendar, Support Desk, Service Management, the report generator, automation rules and dashboard widgets — with live autocomplete on fields and values, and validation that returns an error with a character position rather than silently dropping a clause.

Can TQL query ticket history?

Yes. TQL has five historical operators — WAS, WAS NOT, CHANGED, CHANGED FROM and CHANGED TO — backed by the activity log and supported on status, assignee, priority, resolution and type.

They combine freely with ordinary clauses and with relative dates, so status WAS "In Review" AND resolved > -1q is a single query rather than a report.

What happens when I write an invalid query?

Validation returns an error with a position in the query string. It names unknown fields and suggests the closest real one, catches unbalanced parentheses, unclosed quotes, an operator that is invalid for the field's type, a historical operator on a field that does not support one, and a missing value.

A clause is never silently dropped. A query either runs exactly as written, or it is refused with an explanation.

Can I ask for tickets in plain language instead of TQL?

Yes. The TQL badge toggles to AI mode: you ask a question in plain language and Trakr returns generated TQL — never results. The query appears under the search bar, editable as ordinary TQL, and it is run through the same parser and validated before it is handed back.

A people resolver injects your organisation's own directory into the prompt so "assigned to Luc" becomes the real username. Only unambiguous names are rewritten, customers are excluded, and currentUser() and lists are left alone.

Where can I use TQL in Trakr?

The global search bar with autocomplete, recent searches, a saved-filter dropdown and a / shortcut; the advanced search page with a scope selector for All, Projects or Support Desk; the ticket list, board, roadmap and calendar; the Support Desk queue and Service Management; the report generator and saved reports; automation rule selection; and SAVED_FILTER dashboard widgets.

Can I save and share a query?

Saved filters come in three flavours: personal, shared organisation-wide, and favourite. A saved filter can be picked from the search bar's dropdown, driven into a dashboard widget, used as the selection filter for an automation rule, or scheduled as a report with its figures delivered in the mail body.

Ask a hard question. Get a readable answer.

TQL ships in every Trakr tier, on every surface, with no add-on and no query limits.