Filtering, sorting, search, and pagination
View as MarkdownThis page builds one deterministic task query from filters, search, sort, and pagination. You will call it through HTTP, reproduce it in the generated grid, and see why invalid query state fails instead of broadening the result. These mechanics support bookmarkable worklists, exports, saved views, and predictable integrations.
Show open launch tasks for one project, sorted by due date and split into small pages. Make the browser view and generated API use the same query.Compose the query
Section titled “Compose the query”Generated list and export routes use one strict grammar:
GET /api/tables/tasks?filter[project_id][eq]=1&filter[status][in]=open&q=launch&sort=due_date,-id&page=1&limit=25- Every filter includes a column and operator:
filter[column][operator]=value. - Select-backed columns use
inandninwith a comma-separated option list. qsearches only columns declared inmeta.searchand combines with filters using AND.sort=due_date,-idorders due date ascending, then ID descending.pageis one-based.limitaccepts values from 1 through 1000 and defaults to 50.
Bracket characters may need URL encoding in a shell or client. This curl form
keeps the query readable while encoding it correctly:
curl --get "http://localhost:3000/api/tables/tasks" \ --data-urlencode "filter[project_id][eq]=1" \ --data-urlencode "filter[status][in]=open" \ --data-urlencode "q=launch" \ --data-urlencode "sort=due_date,-id" \ --data-urlencode "page=1" \ --data-urlencode "limit=25"Use the authenticated browser session, an API token, or the Sapporta CLI when the app is protected. The successful response includes the current page and total count:
{ "data": [ { "id": 7, "project_id": 1, "title": "Publish launch checklist", "status": "open", "due_date": "2026-08-01" } ], "meta": { "total": 1, "page": 1, "limit": 25, "pages": 1 }}Generated table screens serialize the same query state in the URL. Open
/tables/tasks, select the project and open status, search for launch, set
due-date sort, and refresh. The controls and result should survive because the
URL owns the current query state. CSV export uses the active filter, search, and
sort rather than silently exporting all visible rows.
Status is select-backed text, so its in and nin value editor is a searchable
multi-value combobox. The input query filters the options derived from the
Drizzle enum declaration. Chosen values appear as removable chips, and only
those chosen values enter the filter draft. Search text itself never becomes a
filter value.
Let invalid queries fail
Section titled “Let invalid queries fail”Unknown columns, unsupported operators, malformed semantic values, search on a table without search metadata, and invalid page or limit values return a structured 400 response. A caller must correct the query. Retrying after dropping a rejected filter can expose or export a much larger result set.
GET /api/tables/tasks?filter[status]=openThe request above is invalid because it omits the operator bracket. For typed
frontend table code, use TypedFilterCondition with encodeTypedFilters() at
the URL boundary, and use parseFiltersForTable() when restoring URL filters
against table metadata. That keeps numbers, booleans, dates, timestamps, and
lookup IDs typed until serialization.
The task query now has one meaning in the URL, generated grid, HTTP route, and export. Its strict failure behavior prevents a typo from turning into an unfiltered read. Next, use the same metadata to create and edit records through generated forms.