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.

GET /products — page 2, 50 per page
1GET /products?page=2&limit=50
pagenumberOptional

1-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.

Default1
limitnumberOptional

Page 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.

Default20
offsetnumberOptional

Explicit 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[] | stringOptional

The 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" }]

operatorFilterOperatorOptional

One 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 · neqEquals / not equals.
  • gt · gte · lt · lteGreater / less than, with inclusive variants.
  • like · ilikeSQL pattern match; ilike is case-insensitive.
  • in · notInMembership test against an array of values.
  • isNull · isNotNullNULL presence check; the value field is ignored.
searchstringOptional

A 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[]Optional

Which 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[] | stringOptional

An 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[] | stringOptional

Restrict 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"]

distinctbooleanOptional

Return only distinct rows — applies SELECT DISTINCT across the selected columns.

Defaultfalse
distinctOnstring[] | stringOptional

Postgres 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.

Structured query via the typed client
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[] | stringOptional

The relations to expand. Each RelationQuery names a relation and may add its own columns, where, limit and a nested with for deeper graphs.

with[].namestringRequired

The relation key as defined in relations.ts (e.g. 'author', 'comments').

with[].columnsstring[]Optional

Restrict which columns of the related rows come back.

with[].whereFilterCondition[]Optional

Filter the related rows using the same condition shape as top-level filters.

with[].limitnumberOptional

Cap how many related rows load per parent record.

with[].withRelationQuery[]Optional

Nest 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.

successbooleanOptional

Whether the operation succeeded. The client uses this to route into your onAfterHandle vs onErrorHandle callbacks.

dataT | PaginatedResult<T>Optional

The payload. For lists it's { items, meta }; for single reads and mutations it's the record itself.

messagestringOptional

A human-readable status message, surfaced in toasts or logs.

errorError | stringOptional

Present only on failure; describes what went wrong.

data.metaPaginationMetaOptional

On lists: page, limit, offset, totalItems, totalPages, hasNextPage, hasPrevPage, nextPage and prevPage — everything a pager needs, pre-computed.

Related sections