A query is a read: it returns data and never mutates anything. Where a command names the aggregate it writes, a query names the entity it returns and the method it reads by — either a single record (get) or a paginated page (list). Because reads and writes are separate constructs, a query is free to be shaped purely for how it will be consumed.
The simplest query loads one record by id:
query GetProduct {
returns Product
method get
visibility public
doc "Load a product by id."
systems [CatalogSystem]
where "this.id == id"
id uuid required
}where — binding inputs to the read
The where clause is the query's filter, written over this (the candidate row) and the query's own inputs. In GetProduct, where "this.id == id" matches the row whose id equals the id input — a lookup by primary key. The input fields at the bottom (id uuid required) are exactly the same input mechanism a command uses; here they parameterize the filter instead of a write.
A where can bind any field. Chowk's ListOrdersByCustomer scopes the result to one customer:
query ListOrdersByCustomer {
returns Order
method list
visibility public
doc "List a customer's orders, most recent first."
systems [OrderingSystem]
where "this.customer_id == customer_id"
pagination offset
default_page_size 20
default_sort [-created_at]
customer_id uuid required
}get versus list
The method decides the shape of the response:
getreturns a single record (or a not-found). Use it for lookups by a unique key —GetProduct,GetOrder.listreturns a page of records plus a total count, so a client can render a table and paginate through it. Use it for collections —ListProducts,ListOrdersByCustomer.
A list query carries three extra knobs that a get does not:
| Clause | What it controls |
|---|---|
pagination offset | how pages are walked — offset-based paging |
default_page_size 20 | the page size when the client doesn't ask for one |
default_sort [-created_at] | the default ordering — here, newest first (the leading - means descending) |
ListProducts shows the same three on the catalog side, with no where at all — an unfiltered, newest-first listing of the whole catalog:
query ListProducts {
returns Product
method list
visibility public
doc "List catalog products, most recent first."
pagination offset
default_page_size 20
default_sort [-created_at]
}Reads honor the same boundaries as writes
A query is not a way around your access rules. It runs under the same resolved caller as a command, so tenancy isolation and authorization apply to reads exactly as they do to writes: a caller only ever sees rows their identity is allowed to see, and a query can carry a policy [...] belt to gate the read itself (see Policies). Modeling a read as a first-class query — rather than exposing the table — is what lets those boundaries hold on the read side too.