Easy Query Language¶
Easy Query Language (EQL) is a Jira-like text filter available on every EasyQuery. Users type a condition instead of filling in the filter panel. The text is stored in easy_queries.query_string and applied in EasyQuery#statement next to the regular filters. If you extend filters of your own query, read How EQL maps to filters.
Syntax¶
FIELD OPERATOR VALUE [AND|OR FIELD OPERATOR VALUE] ...
NOT status = Closed AND (assignee IS EMPTY OR assignee IN membersOf("Support"))
- Fields: The column label as shown in the application, in any language of the instance (
Status,Stav,"Due date"; quote multi-word labels). Custom fields by name ("Customer"). Short English aliases for issue queries (status,assignee,reporter,priority,tracker,project,created,updated,due,done,milestone, and others). Raw identifiers (status_id,cf_12). - Operators:
=,!=,>,<,>=,<=,IN (...),NOT IN (...),IS EMPTY,IS NOT EMPTY,~(contains),!~,^~(starts with),$~(ends with). Every filter-panel operator has a spelling too, so any filter can be written as a query:IS OPEN/IS CLOSED(statuso/c),IS OPEN/IS NOT OPENon relations (*o/!o),HAS ALL (a, b)(&),ONLY IN (project)(only=),IN PROJECT (p)/NOT IN PROJECT (p)(=p/!p),OUTSIDE PROJECT p(=!p),DUE IN 7(>dd). The operators a field accepts depend on its filter type (EasyQuery.operators_by_filter_type). - Values: Labels (
New,"In Progress") or ids;me(localised, for examplejá) for people; numbers; ISO dates (2026-01-31,2026-01-31T10:30); relative dates (-7d,+2w,-1M,-1y); the localised date shortcuts of the date filter (today,yesterday,tomorrow,"this week","last month","this year"). A shortcut is a range:= "this week"means between its start and end,< "this week"before its start,> "this week"after its end. - Functions:
currentUser(),meAndMyGroups(),membersOf("Group"),now(),startOfDay(),endOfDay(),startOfWeek(),endOfWeek(),startOfMonth(),endOfMonth(),startOfYear(),endOfYear(). Date functions accept an offset:startOfWeek(-1w).
The editor on list pages suggests fields, operators, values and functions while you type, and its help dialog (? after the field) shows examples built from the data of your instance.
How values are resolved¶
A numeric id is used exactly as written, like the id a filter panel stores. It is not checked for existence or visibility, so a locked assignee or a deleted status matches nothing, as in the filter panel.
A name is resolved among the records that the autocomplete of the filter offers. For people these are active, visible principals, so a locked account is reachable by id only. Names resolve in a fixed order: unique key (login for principals, identifier for projects), display name among active records (exact case first), display name among the other records. When several records tie on the same level, the value is rejected with the error code AMBIGUOUS_VALUE, and the error lists replacement candidates the editor offers as one-click fixes.
A login is accepted as input from everybody, but it is shown only to users who administer users (User#easy_lesser_admin_for?(:users)). Everybody else sees the record id.
Error messages are localised under easy_query_language.errors.*.
Saved queries and permissions¶
A saved query keeps record ids, not names, in query_string: before saving, EasyQuery rewrites the names and logins of association values to ids, so a public query never carries the name of a record its reader cannot see. The editor receives EasyQuery#readable_query_string, which turns the ids of records visible to the current user back into labels. Values that do not resolve (unknown or ambiguous names, me, functions, static lists) stay as written.
An invalid saved query fails closed: EasyQuery#statement returns 1=0 and the error is shown above the list.
Authorisation, the page or project context and the classic filters stay outside the EQL expression and are combined with it using AND. EQL cannot widen what the page already restricts.
The GraphQL queries easyQueryLanguageSuggestions, easyQueryLanguageValidation, easyQueryLanguageHelp and filterValues check EasyQuery.metadata_visible? and project visibility, and return an empty result when access is denied. The default policy requires permission_view_entities in the project context or globally. If your query declares no such permission and its metadata is sensitive, override self.metadata_visible?(user, project:) in the query class.
How EQL maps to filters¶
EQL builds ordinary conditions through EasyQuery#filter_statement, so a filter you define with add_available_filter works in EQL without extra code. ~ keeps % and _ as wildcards, and strict < and > keep the NULL and time zone handling of the classic operators.
Filters that the classic query assembles outside filter_statement (for example subproject_id and is_planned of issues, parent_id, member_id and role_id of projects) are independent EQL conditions. If your query has such a filter, implement two hooks:
query_string_scope_operators(field)returns the operators the filter accepts. The default is none, so EQL rejects the filter until you list them.- The protected
query_string_scope_predicate(field, operator, values)returns an Arel node; the caller negates it for!=andNOT IN. Without it, EQL raisesNotImplementedErrorfor that filter.
def query_string_scope_operators(field)
field == "my_scope_id" ? ["=", "!=", "IN", "NOT IN"] : super
end
Each of these filters accepts equality and list comparisons plus the operators that make sense for it; unsupported combinations fail validation instead of being ignored. Examples with the issue scope filters:
subproject_id = "Project A" OR is_planned = true
NOT (subproject_id = "Project A" AND is_planned = true)
subproject_id = Aselects A and its subtree;!=andNOT INnegate that selection inside the outer context.- Project
parent_id = Aselects the descendants of A, not A itself. - A missing numeric id gives an empty positive selection, not a no-op.
- Project and version
member_idandrole_idare independent conditions; they do not require the selected member to hold the selected role.
OUTSIDE PROJECT on relations takes exactly one project (id, identifier or name) and matches issues that have a relation to an issue outside that project. NOT IN PROJECT A means no relation into A and also matches issues without relations.