Query API
Filter, sort, paginate & expand relations
Declaring an entity does more than create a table — it mounts a list endpoint that understands a full query language. The same StandardQueryParams shape is accepted by every generated collection route, parsed from the query string, validated, and translated into a safe parameterised Drizzle query.
Nothing here is hand-written per entity. Filtering, searching, sorting, field selection, pagination and nested relation loading all come for free the moment a table exists, and every response is wrapped in the same typed StandardReturn envelope so the client always knows what it's getting.
Parameters can be sent as structured objects (from the typed client) or as JSON strings (handy for raw URLs) — filters, sort and with all accept either form.
Pagination#
List endpoints are paginated by default. Ask for a page, or drive it with a raw offset — the response always carries a meta block with the totals and the next/prev cursors so a table or infinite list never has to compute them itself.
1GET /products?page=2&limit=50pagenumberOptional1-based page number. Combined with limit it derives the SQL offset for you. Out-of-range pages return an empty items array with an honest meta block rather than an error.
1limitnumberOptionalPage size — how many rows to return. A sane upper bound is enforced server-side so a client cannot ask for the entire table in one request.
20offsetnumberOptionalExplicit row offset. When provided it takes precedence over page, which is useful for cursor-style or virtualised lists that think in absolute positions.
Filtering#
filters is an array of { field, operator, value } conditions combined with AND. Twelve operators cover equality, comparison, pattern matching, set membership and null checks. Every value is bound as a parameter — there is no string interpolation, so the surface is injection-safe by construction.
filtersFilterCondition[] | stringOptionalThe conditions to apply. Pass an array of { field, operator, value } objects, or a JSON-encoded string of the same. Unknown fields are rejected so a typo can't silently match everything.
Example: [{ "field": "status", "operator": "eq", "value": "active" }]
operatorFilterOperatorOptionalOne of twelve operators. eq/neq compare equality; gt/gte/lt/lte compare order; like/ilike match SQL patterns (ilike is case-insensitive); in/notIn test set membership against an array value; isNull/isNotNull test for NULL and ignore value.
eq · neq— Equals / not equals.gt · gte · lt · lte— Greater / less than, with inclusive variants.like · ilike— SQL pattern match; ilike is case-insensitive.in · notIn— Membership test against an array of values.isNull · isNotNull— NULL presence check; the value field is ignored.
searchstringOptionalA single free-text term applied across searchFields with a case-insensitive partial match — the one-box search a UI usually wants, without composing filters by hand.
searchFieldsstring | string[]OptionalWhich columns search scans. Accepts a comma-separated string or an array. When omitted, the entity's configured default text columns are used.
Example: ["name", "description"]
Sorting & field selection#
Order results by one or many columns, trim the payload to just the fields you render, and collapse duplicates — each independently controllable per request.
sortSortCondition[] | stringOptionalAn ordered list of { field, direction } where direction is 'asc' or 'desc'. Multiple entries sort by the first, then break ties with the next, and so on.
Example: [{ "field": "createdAt", "direction": "desc" }]
selectstring[] | stringOptionalRestrict the returned columns. Fetching only what a list view shows keeps payloads small and avoids leaking columns the UI never displays.
Example: ["id", "name", "price"]
distinctbooleanOptionalReturn only distinct rows — applies SELECT DISTINCT across the selected columns.
falsedistinctOnstring[] | stringOptionalPostgres DISTINCT ON: keep the first row per unique combination of these columns (respecting sort), ideal for de-duplicating joined result sets.
Relation loading#
with eager-loads related records defined in your Drizzle relations file. Each relation can itself be filtered, column-trimmed, limited and nested — so one request can return a record with its children and grandchildren, shaped exactly as the screen needs.
1actions.GET_POSTS.start({2 payload: {3 page: 1,4 limit: 20,5 filters: [{ field: "published", operator: "eq", value: true }],6 sort: [{ field: "createdAt", direction: "desc" }],7 with: [8 { name: "author", columns: ["id", "name"] },9 { name: "comments", limit: 3, with: [{ name: "author" }] },10 ],11 },12 onAfterHandle: (res) => {13 console.log(res.data.items, res.data.meta.totalPages);14 },15});withRelationQuery[] | stringOptionalThe relations to expand. Each RelationQuery names a relation and may add its own columns, where, limit and a nested with for deeper graphs.
with[].namestringRequiredThe relation key as defined in relations.ts (e.g. 'author', 'comments').
with[].columnsstring[]OptionalRestrict which columns of the related rows come back.
with[].whereFilterCondition[]OptionalFilter the related rows using the same condition shape as top-level filters.
with[].limitnumberOptionalCap how many related rows load per parent record.
with[].withRelationQuery[]OptionalNest another relation query to load grandchildren — the structure is fully recursive.
The response envelope#
Every endpoint — query or mutation — resolves to a StandardReturn<T>. List endpoints specialise it to PaginatedReturn<T>, where data is a PaginatedResult with both the items and a ready-made meta block.
successbooleanOptionalWhether the operation succeeded. The client uses this to route into your onAfterHandle vs onErrorHandle callbacks.
dataT | PaginatedResult<T>OptionalThe payload. For lists it's { items, meta }; for single reads and mutations it's the record itself.
messagestringOptionalA human-readable status message, surfaced in toasts or logs.
errorError | stringOptionalPresent only on failure; describes what went wrong.
data.metaPaginationMetaOptionalOn lists: page, limit, offset, totalItems, totalPages, hasNextPage, hasPrevPage, nextPage and prevPage — everything a pager needs, pre-computed.
Related sections