Skip to content

Search and JQL reference

Orbit searches work with a JQL-style query language. This page documents the subset Orbit implements. It is a typed language compiled to parameterized SQL: nothing from a query is pasted into SQL text. It is not a complete copy of Jira’s JQL, and an unsupported construct returns an explicit error.

Open work assigned to me, most urgent first
project = ORB AND (status = "To Do" OR status = "In Progress")
AND assignee = currentUser() ORDER BY priority DESC, updated DESC
Bugs by priority and label
type = bug AND priority IN (highest, high) AND labels = frontend
Unassigned work in an open sprint
assignee IS EMPTY AND sprint IN (openSprints())
Recent work mentioning a word
created >= -7d AND text ~ "workspace" ORDER BY created DESC
  • OR binds loosest, then AND, then NOT. !, && and || are accepted. Parentheses group.
  • Words are case-insensitive.
  • Values with spaces or reserved characters are quoted with " or '. Inside quotes the escapes are \", \', \\, \n, \t, \r and \uXXXX.
  • Reserved words (AND, OR, NOT, IN, IS, EMPTY, NULL, ORDER, BY and others) must be quoted to be used as values.
  • Limits: 10,000 characters, 500 terms, 100 values in a list, 40 levels of nesting and 5 ORDER BY fields.

project, key (also issuekey, issue), summary, description, text, type (issuetype), status, statusCategory, priority, resolution, assignee, reporter, watcher, sprint, epic ("epic link"), parent, labels (label), component (components), fixVersion (fixversions), storyPoints ("story points"), rank, created, updated, resolved, due (duedate) and startDate.

Custom fields you can see are used by name (quoted when it has spaces) or as cf[<fieldId>]. Same-named custom fields of one kind, for example one per project, are searched together. Same-named fields of different kinds are an error that names each cf[...].

Field type Operators
Text ~ and !~ (contains, ignoring case; * matches any characters and ? one), = and != (whole value, ignoring case)
Numbers, dates, priority, keys also <, <=, >, >=. Priority compares by its position in the priority list. Keys compare within the key’s project.
Every field except text IN and NOT IN
Fields that can be empty IS EMPTY, IS NOT EMPTY (or NULL)

As in Jira, != and NOT IN never match an empty field. Multi-valued fields (labels, components, versions, watchers, multi-select custom fields) match when any value matches. A comparison the field’s type does not support is an error, never an empty result.

Custom fields follow their kind: text, paragraph and URL as text; number; date; date-time; select and multi-select by option name or id (an unknown option is an error that lists the options); cascading by parent or child option; user; group (by id or name); and checkbox (true or false).

Function Meaning
currentUser() The person running the query
membersOf(group) Members of a group
openSprints(), futureSprints(), closedSprints() Sprints by state
releasedVersions([project]), unreleasedVersions([project]) Versions by state
latestReleasedVersion([project]), earliestUnreleasedVersion([project]) One version
now() The current moment
startOfDay, endOfDay, startOfWeek, endOfWeek, startOfMonth, endOfMonth, startOfYear, endOfYear Period boundaries, with an optional increment such as (-1) or ("+2w")

A list function may follow IN without parentheses. = and != with a list function mean IN and NOT IN, so older saved filters that write sprint = openSprints() still work. An unknown function, or one used on a field of another type, is an error.

2026-10-31, 2026/10/31 and "2026-10-31 14:30" are read in the time zone of your profile (UTC when none is set), and so are the start and end functions. A period from now such as -7d, 2w, -4h or -1w2d is relative to the moment of the search. Units are w, d, h and m. Increments of the start and end functions also take M and y.

ORDER BY takes up to five fields, each ASC or DESC, on sortable fields including rank, priority (by position), key (project key, then number), and number, date and text custom fields. Empty values sort last in either direction. Every sort ends with the work item id, so pages are stable. Without ORDER BY, results come newest-updated first.

status, assignee, reporter, priority, resolution, fixVersion and sprint keep a searchable history. Other fields refuse WAS and CHANGED and list the fields that have history.

Form Matches
field WAS value Work whose field held the value at any time, now included
field WAS IN (...) Held any of the values
field WAS NOT value, WAS NOT IN (...) Never held it
field CHANGED The field changed after creation

Predicates may come in any order, each at most once:

  • AFTER date, BEFORE date, ON date (that day in your time zone) and DURING (from, to) (a date-only to includes that day)
  • BY person or BY (person, ...): names, e-mails, ids, currentUser() or membersOf()
  • for CHANGED only, FROM value and TO value
History examples
status WAS "In Progress" DURING (startOfMonth(-1), endOfMonth(-1))
assignee CHANGED BY currentUser() AFTER -7d
priority CHANGED FROM low TO high
NOT status CHANGED

text ~ fullText("release planning") is an Orbit extension that uses an index over the summary and the description. It takes one quoted argument of 1 to 500 characters, only on text with a positive ~. Negate the clause with NOT (text ~ fullText("query")). A quoted phrase, OR and a minus-prefixed exclusion work inside the argument. A query with full-text clauses and no ORDER BY returns the best matches first; an explicit ORDER BY always wins.

Word forms are found: with the default languages “planning” finds “plan”. Site administrators choose one to three search languages from 30 PostgreSQL configurations. Chinese, Japanese and Korean have no word breaks and are found by substring. Comments, attachments and custom fields are not full-text indexed, and there is no fuzzy matching.

The JQL editor on the search page suggests fields, operators, functions, values and keywords for the word at the cursor (arrow keys, Enter or Tab, Escape, Ctrl+Space). It checks the query as you type and marks the error in place. Switching to the basic mode converts a query that basic filters can express, or says why it stays in JQL.

An invalid query returns HTTP 400 with code: "invalid_jql" and the position and end of the problem, counted in Unicode code points from 0.

  • Search, value suggestions, exports and saved-board columns stop after 8 seconds and answer HTTP 503 with advice to narrow the query.
  • A page has at most 200 results, and startAt is at most 100,000.
  • Results are cached for 15 seconds per person. Add consistency=primary to read straight from the database.

The search page keeps its state in the URL: smode, stext, stextmode, sproject, stype, sstatus, sassignee, ssort, jql, spage and ssize. A link such as /?jql=status%3Ddone&spage=2#search opens that search and page.

GET /search/jql/manifest lists what the engine supports: fields with aliases, types and operators, functions, keywords, limits, and what is unsupported, including the custom fields you can see. POST /search/jql/parse with { "jql": "..." } checks a query without running it, and GET /search/jql/suggestions?field=&value= returns up to 15 values for a field. See the API overview.

Run a query
curl -H "Authorization: Bearer opt_..." \
"https://acme.orbit.inortix.com/api/v1/search?jql=project%3DORB%20AND%20type%3Dbug&startAt=0&maxResults=50"

All of the above is built. Saved filters and subscriptions are covered on their own page.