Skip to content

Filtering, sorting, search, and pagination

View as Markdown

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

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 in and nin with a comma-separated option list.
  • q searches only columns declared in meta.search and combines with filters using AND.
  • sort=due_date,-id orders due date ascending, then ID descending.
  • page is one-based. limit accepts 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:

Terminal window
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.

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]=open

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