ObjectQL
Das strukturierte Abfrageformat, das von /api/v1/data/*, Views, Reports und KI-Tools verwendet wird.
ObjectQL
ObjectQL ist das JSON-Abfrageformat, das die Daten-Engine verarbeitet. Es ist das,
was /api/v1/data/:object akzeptiert, wozu Views kompiliert werden, wozu Reports
serialisiert werden und was das KI-Tool query_data erzeugt.
Zwei Formen:
- Einfache Liste —
where,orderBy,limit,expandusw. als Query-String-Parameter anGET /api/v1/data/:objectübergeben. - Erweiterte Abfrage — den vollständigen Abfragekörper an
/api/v1/data/:object/queryper POST senden (erforderlich fürgroupByundaggregations).
Abfrageform
{
object: string, // required — target object name
fields?: string[], // projection (default: all visible fields)
where?: FilterCondition, // see Filters
orderBy?: SortNode[], // [{ field, order: 'asc' | 'desc' }]
limit?: number, // page size
offset?: number, // offset pagination
cursor?: string, // cursor pagination (preferred)
expand?: string[], // batch-resolve relation fields
joins?: JoinNode[], // explicit joins (inner | left | right | full)
groupBy?: (string | GroupByNode)[],
aggregations?: AggregationNode[], // sum/avg/count/min/max/...
having?: FilterCondition, // filter after aggregation
distinct?: boolean
}Schema-Quelle: packages/spec/src/data/query.zod.ts.
Filter
where ist ein Baum von Bedingungen. Feldoperatoren werden mit
$ präfixiert; logische Verknüpfungen ($and, $or, $not) stehen auf der obersten Ebene.
Vergleich
| Operator | Beispiel | Hinweise |
|---|---|---|
$eq | { status: { $eq: "open" } } | Gleichheit |
$ne | { priority: { $ne: "low" } } | Ungleichheit |
$gt, $gte, $lt, $lte | { amount: { $gte: 1000 } } | Vergleiche |
Alle Vergleichsoperatoren akzeptieren auch eine Feldreferenz für
feldübergreifende Vergleiche: { end_at: { $gt: { $field: "start_at" } } }.
Menge & Bereich
| Operator | Beispiel |
|---|---|
$in | { status: { $in: ["new","open"] } } |
$nin | { owner_id: { $nin: ["u_1","u_2"] } } |
$between | { created_at: { $between: ["2026-01-01","2026-02-01"] } } |
Zeichenkette
| Operator | Beispiel | Hinweise |
|---|---|---|
$contains | { subject: { $contains: "refund" } } | Groß-/Kleinschreibung wird ignoriert |
$notContains | { notes: { $notContains: "test" } } | |
$startsWith | { email: { $startsWith: "@" } } | |
$endsWith | { email: { $endsWith: "@acme.com" } } |
Null & Existenz
| Operator | Beispiel |
|---|---|
$null | { closed_at: { $null: true } } |
$exists | { external_id: { $exists: false } } |
Logische Verknüpfungen
{
"$and": [
{ "status": { "$eq": "open" } },
{
"$or": [
{ "priority": { "$in": ["high", "urgent"] } },
{ "due_at": { "$lt": { "$cel": "now()" } } }
]
}
]
}CEL-Ausdrücke können mit { "$cel": "..." } eingebettet werden — nützlich für
„zeitrelative" Filter, die der Server zur Abfragezeit auswertet.
Sortierung
"orderBy": [
{ "field": "priority", "order": "desc" },
{ "field": "created_at", "order": "asc" }
]Query-String-Kurzform: ?orderBy=-priority,created_at.
Paginierung
Cursor (empfohlen). Die Antwort enthält nextCursor; gib ihn
bei der nächsten Anfrage als cursor zurück.
GET /api/v1/data/ticket?limit=50&orderBy=-created_at
→ { items: [...], nextCursor: "eyJ..." }
GET /api/v1/data/ticket?limit=50&cursor=eyJ...Offset. Einfacher, verschlechtert sich aber bei großen Tabellen.
GET /api/v1/data/ticket?limit=50&offset=200Die Laufzeitumgebung begrenzt limit pro Objekt über ObjectSpec.maxPageSize
(Standard 200).
Relationen — expand
expand löst Fremdschlüssel im Stapel auf, sodass keine N+1-Abfragen entstehen.
{
"object": "support_ticket",
"expand": ["assignee", "customer.account"],
"limit": 20
}Liefert jedes Ticket mit assignee und customer.account als
verschachtelte Objekte zurück, statt nur als IDs.
Joins
Für Ad-hoc-Joins außerhalb des Metadaten-Graphen:
"joins": [
{ "type": "left", "object": "user", "as": "u", "on": "assignee_id = u.id" }
]type: inner | left | right | full. Verbundene Tabellen sind
in where, orderBy und aggregations über den as-Alias
zugänglich.
Aggregation
Nur POST /api/v1/data/:object/query.
{
"object": "order",
"where": { "status": { "$ne": "cancelled" } },
"groupBy": [
"customer_id",
{ "field": "created_at", "dateGranularity": "month" }
],
"aggregations": [
{ "function": "sum", "field": "amount", "alias": "total_sales" },
{ "function": "count", "alias": "order_count" }
],
"having": { "total_sales": { "$gt": 10000 } },
"orderBy": [{ "field": "total_sales", "order": "desc" }],
"limit": 25
}Funktionen: count, sum, avg, min, max, count_distinct,
array_agg, string_agg.
Datumsgranularität (für zeitlich gebucketete Gruppierung):
day | week | month | quarter | year.
Distinct
{ "object": "ticket", "fields": ["status"], "distinct": true }Suche
Die Volltextsuche über searchable: true-Felder wird unter
GET /api/v1/search?q=...&object=ticket bereitgestellt. Objektspezifische Scoring-Regeln
werden in der Objekt-Spezifikation konfiguriert.
Wo ObjectQL zum Einsatz kommt
GET /api/v1/data/:object— Query-String-FormPOST /api/v1/data/:object/query— vollständiger Körper, unterstützt Aggregationen- View-Definitionen (
filter,sort) — werden zu ObjectQL kompiliert - Reports — werden zu ObjectQL serialisiert
- Das KI-Tool
query_data— erzeugt einen ObjectQL-Körper für die Freigabe-Warteschlange
Siehe auch
- REST API — Endpunkte, die ObjectQL verarbeiten
- Field types — was abfragbar ist
- CEL — Ausdruckssprache, die in
$cel-Filtern verwendet wird @objectstack/spec/data/query.zod.ts— maßgebliches Schema
REST API
Die HTTP-Schnittstelle, die ObjectOS bereitstellt — aus deinen Metadaten generiert, durch Berechtigungen eingegrenzt, per OpenAPI beschrieben.
CEL-Ausdrücke
Die Ausdruckssprache, die für Formeln, Prädikate, Zeitpläne und vorlagenbasierte Zeichenketten verwendet wird — bereitgestellt über fünf getaggte Templates.