Search
Add case-insensitive search to one paginated plugin table with withPagination and the shared table UI.
withPagination includes built-in multi-column search filtering. When search columns are specified, incoming ?search= query parameters automatically apply case-insensitive ILIKE clauses across the chosen columns.
Site-Wide Search
For site-wide search across multiple collections and models, see Search & Discovery.
Quick start
ContentDataTable is VitNode's reusable table UI: it renders rows, toolbar,
search, sorting, and pagination. It does not fetch data. See Data
Table for the navigation provider it needs around it.
Accept search in the route query
Extend zodPaginationQuery to accept the optional search string:
import { z } from "@hono/zod-openapi"
import { zodPaginationQuery } from "@vitnode/core/api/lib/with-pagination"
request: {
query: zodPaginationQuery.extend({
search: z.string().optional(),
}),
}Supply searchable columns
List the columns to filter in the search array:
const data = await withPagination({
c,
params: { query },
table: reactions,
primaryCursor: reactions.id,
search: [reactions.emoji, reactions.description], // Filters across both columns
query: async ({ cursorSelection, limit, where, orderBy }) =>
await c
.get('db')
.select({ ...getColumns(reactions), ...cursorSelection })
.from(reactions)
.where(where)
.orderBy(orderBy)
.limit(limit),
})Show the table search input
Enable the shared table UI after its route loader returns { edges, pageInfo }:
<ContentDataTable
id="reactions-table"
columns={columns}
edges={data.edges}
pageInfo={data.pageInfo}
order={{ defaultOrder: { column: 'emoji', order: 'asc' } }}
search
searchPlaceholder="Search reactions..."
/>The table automatically updates the ?search= query parameter as the user types with debouncing.