Query Language
Query Language Specification
Section titled “Query Language Specification”Thogits uses a JSON-based query language for filtering thogits and tags. Filters are passed as the filter field in search and bulk operation requests.
Every filter is a single JSON object with exactly one key that determines its type.
Thogit Filters
Section titled “Thogit Filters”Logical Combinators
Section titled “Logical Combinators”Combine filters using and, or, and not.
{ "and": [filter, filter, ...] }{ "or": [filter, filter, ...] }{ "not": filter }and requires all children to match. or requires at least one. not inverts.
Combinators nest arbitrarily:
{ "and": [ { "has_tag": "Task" }, { "or": [ { "Task.priority": { "gte": 8 } }, { "not": { "Task.status": { "eq": "Backlog" } } } ]} ]}Full-Text Search
Section titled “Full-Text Search”{ "search": "keyword" }Case-insensitive substring match against both name and description. Matches if either contains the search text.
Tag Membership
Section titled “Tag Membership”{ "has_tag": "Task" }Matches thogits that have the specified tag applied. The value can be a tag name or a tag ULID.
Tag inheritance is respected: if tag B extends tag A, a thogit with tag B also satisfies { "has_tag": "A" }.
Name Filter
Section titled “Name Filter”{ "name": <TextFilter> }Filters by the thogit’s name. See Text Filters below.
Description Filter
Section titled “Description Filter”{ "description": <TextFilter> }Filters by the thogit’s description. If the thogit has no description, the filter does not match (except with not).
Field Filters (Dot Notation)
Section titled “Field Filters (Dot Notation)”{ "Tag.fieldName": <FieldFilter> }Access a tag’s field using TagName.fieldName or TagULID.fieldName. Tag names and ULIDs are interchangeable wherever a tag is referenced.
{ "Task.priority": { "gt": 5 } }{ "Project.deadline": { "lt": "2025-06-01" } }{ "Bug.resolved": true }If the thogit does not have the specified tag or field, the field value is treated as null.
Reference Traversal
Section titled “Reference Traversal”Follow Reference fields to filter on properties of related thogits using -> syntax:
{ "Task.projectRef->Project.priority": { "gt": 5 } }This reads: “the thogit referenced by Task.projectRef must have Project.priority > 5.”
Multi-Hop Traversal
Section titled “Multi-Hop Traversal”Chain multiple hops with ->:
{ "Task.projectRef->Project.orgRef->Org.tier": { "eq": "Enterprise" } }Maximum depth: 5 hops. Exceeding this returns an error.
Terminal Targets
Section titled “Terminal Targets”The final segment after the last -> can be:
| Terminal | Example |
|---|---|
Tag.field | ...->Project.priority": { "gt": 5 } |
name | ...->name": { "regex": "^Acme" } |
description | ...->description": { "eq": "..." } |
has_tag | ...->has_tag": "Enterprise" |
Every non-terminal segment must be Tag.refField format (a Reference-type field).
Text Filters
Section titled “Text Filters”Used by name, description, and string field filters.
{ "regex": "^Feature.*v2" }Rust-flavour regular expression. Matches if the pattern matches anywhere in the string.
Ordinal Comparison
Section titled “Ordinal Comparison”{ "eq": "exact value" }{ "neq": "not this" }{ "gt": "b" }{ "gte": "b" }{ "lt": "m" }{ "lte": "m" }String comparisons use lexicographic ordering.
Field Filters
Section titled “Field Filters”The filter syntax depends on the field’s schema type. Field filters also support shorthand bare values.
Ordinal Operators
Section titled “Ordinal Operators”Six operators are available for ordered comparisons:
| Operator | Meaning |
|---|---|
eq | Equal |
neq | Not equal |
gt | Greater than |
gte | Greater than or equal |
lt | Less than |
lte | Less than or equal |
By Field Type
Section titled “By Field Type”String
Section titled “String”All text filter forms:
{ "Tag.title": { "eq": "exact" } }{ "Tag.title": { "regex": "pattern" } }{ "Tag.title": { "gt": "a" } }Number
Section titled “Number”Ordinal operators with numeric values:
{ "Tag.priority": { "gt": 5 } }{ "Tag.count": { "eq": 10 } }{ "Tag.score": { "lte": 99.5 } }Boolean
Section titled “Boolean”Direct boolean value (shorthand for eq):
{ "Tag.isActive": true }{ "Tag.isActive": false }Or using eq/neq:
{ "Tag.isActive": { "eq": true } }{ "Tag.isActive": { "neq": true } }eq matches only the exact boolean value. neq matches the opposite value and null/missing fields — so { "neq": true } matches thogits where the field is false or not set at all.
Ordinal operators with ISO 8601 date strings. Two formats are accepted:
- Date only:
YYYY-MM-DD(treated as midnightT00:00:00) - Date-time:
YYYY-MM-DDTHH:MM:SS
{ "Tag.deadline": { "lt": "2025-06-01" } }{ "Tag.createdAt": { "gte": "2025-01-15T10:30:00" } }Date detection is automatic: if the string value parses as a date, it’s compared as a date. Otherwise it falls back to text comparison.
Select (Single)
Section titled “Select (Single)”Select fields store values as {"variant": "Name"}. Filtering compares by ordinal position in the schema’s variant list when using ordinal operators, and by variant name when using regex.
{ "Tag.status": { "eq": "Done" } }{ "Tag.priority": { "gt": "Low" } }{ "Tag.status": { "regex": "In.*" } }With ordinal comparison, if the schema defines variants ["Low", "Medium", "High"], then { "gt": "Low" } matches "Medium" and "High" (positions 1 and 2 are greater than position 0).
MultiSelect
Section titled “MultiSelect”Same operators as Select. Matches if any selected variant satisfies the condition.
{ "Tag.labels": { "eq": "urgent" } }{ "Tag.labels": { "in": ["urgent", "important"] } }Reference
Section titled “Reference”Reference fields store a ULID string pointing to another thogit. Direct filtering is limited to existence checks:
{ "Tag.projectRef": { "exists": true } }{ "Tag.projectRef": { "exists": false } }For filtering on the referenced thogit’s properties, use reference traversal.
Special Operators
Section titled “Special Operators”exists
Section titled “exists”Check whether a field has a value:
{ "Tag.assignee": { "exists": true } }{ "Tag.assignee": { "exists": false } }Check membership in a set of values. Works with Select and MultiSelect fields:
{ "Tag.status": { "in": ["Open", "In Progress"] } }Matches if the field value (or any selected variant for MultiSelect) appears in the array.
Bare Value Shorthand
Section titled “Bare Value Shorthand”Field filters accept bare values as shorthand for { "eq": value }:
| Bare Value | Equivalent |
|---|---|
null | { "exists": false } |
true / false | Boolean match |
42 (number) | { "eq": 42 } |
"text" (string) | { "eq": "text" } |
{ "Tag.priority": 5 }{ "Tag.active": true }{ "Tag.notes": null }Tag Filters
Section titled “Tag Filters”Tag filters are used on the tag search endpoint (POST /api/tags/search). They support a subset of the thogit filter language.
{ "and": [filter, filter, ...] }{ "or": [filter, filter, ...] }{ "not": filter }{ "search": "keyword" }{ "name": <TextFilter> }{ "description": <TextFilter> }search is case-insensitive substring match on both the tag’s name and description.
API Endpoints Using Filters
Section titled “API Endpoints Using Filters”Search Thogits
Section titled “Search Thogits”POST /api/thogits/search{ "filter": { "has_tag": "Task" } }Omitting filter or passing null returns all thogits.
Search Tags
Section titled “Search Tags”POST /api/tags/search{ "filter": { "name": { "regex": "^Project" } } }Bulk Delete
Section titled “Bulk Delete”POST /api/thogits/bulk-delete{ "filter": { "has_tag": "Archived" }, "dry_run": true}Returns { matched_count, affected_count, affected_ids, dry_run }. Set dry_run: true to preview without deleting.
Bulk Apply Tag
Section titled “Bulk Apply Tag”POST /api/thogits/bulk-apply-tag{ "filter": { "has_tag": "Task" }, "tag": { "tag_ref": { "Existing": "<tag-ulid>" }, "field_values": {} }, "dry_run": false}Bulk Remove Tag
Section titled “Bulk Remove Tag”POST /api/thogits/bulk-remove-tag{ "filter": { "Task.status": { "eq": "Done" } }, "tag_id": "<tag-ulid>", "dry_run": false}Bulk Update Field Values
Section titled “Bulk Update Field Values”POST /api/thogits/bulk-update-fields{ "filter": { "has_tag": "Task" }, "tag_id": "<tag-ulid>", "field_values": { "priority": 1 }, "merge": true, "dry_run": false}Template Extraction
Section titled “Template Extraction”When creating a new thogit within a filtered view, the system extracts sensible defaults from the active filter. For example, a filter { "has_tag": "Task" } will pre-apply the “Task” tag. A filter { "Task.priority": { "eq": 5 } } will pre-set priority to 5.
Negations (not, neq, exists: false) are ignored during extraction since they don’t imply a positive value. For or filters, the first branch with extractable values is used.
Error Handling
Section titled “Error Handling”| Condition | Error |
|---|---|
Empty filter object {} | "Filter object cannot be empty" |
| Unknown filter key | "Unknown filter. Expected: and, or, not, search, has_tag, name, description, or Tag.field" |
| Tag not found | "Tag 'X' not found" |
| Traversal exceeds 5 hops | "Reference traversal exceeds max depth of 5 hops" |
| Invalid dot-notation | "Invalid dot-notation: 'X'" |
| Invalid operator value type | "'gt' requires a number, string, or date" |
Complete Examples
Section titled “Complete Examples”All high-priority tasks not yet done:
{ "and": [ { "has_tag": "Task" }, { "Task.priority": { "gte": 8 } }, { "not": { "Task.status": { "eq": "Done" } } } ]}Thogits whose name starts with “RFC” or that have a description mentioning “proposal”:
{ "or": [ { "name": { "regex": "^RFC" } }, { "description": { "regex": "(?i)proposal" } } ]}Tasks assigned to projects in the Enterprise tier (reference traversal):
{ "Task.projectRef->Project.orgRef->Org.tier": { "eq": "Enterprise" } }Thogits with any value in the “assignee” field:
{ "Task.assignee": { "exists": true } }Bulk preview: which thogits would be deleted?
{ "filter": { "and": [ { "has_tag": "Temp" }, { "Temp.createdAt": { "lt": "2025-01-01" } } ] }, "dry_run": true}