Core concepts

Pagination

How page and per_page are mirrored into params, and how filter changes reset the page.

Pagination is built in. Unless you turn it off, params always includes the page and per-page values, mirrored straight from the URL:

const { params } = useFilters(configs);
// params = { ...filters, page: 1, per_page: 10 }

The keys are yours to name (they default to page / per_page), and they're the same in the URL and in params: there's no separate mapping to maintain. You set them once with createFilters:

export const { useFilters } = createFilters({
  pagination: { pageKey: 'page', perPageKey: 'page_size', defaultPerPage: 25 }
});
// URL: ?page=2&page_size=25   →   params: { page, page_size }

Reading and setting the page

The page and per-page values are plain keys on params. You drive them however your pagination UI works (nuqs directly, or your own state) and spread them into your query. useFilters doesn't render pagination controls; it just keeps the values in the URL and in params.

Resetting on filter change

When a filter changes, the old page of results no longer applies: page 7 of an unfiltered list isn't page 7 of the filtered one. So by default, changing any filter resets the page to its first value:

// User is on page 5, then edits a filter → params.page becomes 1 again.

If your pagination is driven entirely outside the hook and it should never touch the page param, opt out, for the whole app or one screen:

createFilters({ pagination: { resetPageOnFilterChange: false } });
// or per call:
useFilters(configs, { pagination: { resetPageOnFilterChange: false } });

First page and 0-indexed APIs

firstPage is the number your first page counts from: the value when the URL has none, what a reset writes, and the base your backend expects. It defaults to 1; set it to 0 for a 0-indexed API:

createFilters({ pagination: { firstPage: 0 } });
// The first page is ?page=0, in both the URL and params.

This is also how you drive an offset/limit backend directly, with no conversion at all. Name the keys to match and let firstPage: 0 be the starting offset:

createFilters({ pagination: { pageKey: 'offset', perPageKey: 'limit', firstPage: 0 } });
// params: { offset, limit, ...filters }; increment `offset` by `limit` yourself
// (e.g. setFilter('offset', offset + limit)) to page forward.

pageKey / perPageKey don't have to mean "page number"; they're just the two pagination values useFilters mirrors into params and resets on filter change. Only reach for a page-number UI (1, 2, 3…) converting to offset/limit at the fetch call if your UI specifically wants page numbers; if it doesn't, drive the backend's own values directly instead.

Turning pagination off

Pass pagination: false for a call that has no pages (a settings filter, a faceted sidebar with infinite scroll). params then contains only your filters, with no page keys, and filter changes don't reset anything.

Full option details are in the createFilters reference.

On this page