Introduction
Headless, URL-synced filter state for React. What it is and when to use it.
One config object in; URL-synced state and a typed params object out. That is
the whole library in a sentence.
Most list and table screens need the same things: a set of filters, kept in the URL
so the view survives refreshes and can be shared, wired into data fetching so
results stay in sync. Building that by hand means a useState per filter, manual
URL serialization, and remembering to reset the page whenever a filter changes. It
is repetitive and easy to get subtly wrong.
use-filters does that part for you. You describe your filters once; the hook keeps their state in the URL and hands back everything you need to fetch data and render controls. It ships no UI: you build the controls, it owns the state.
The idea in one example
const { params, filters } = useFilters({
status: f.select({ label: 'Status', valueType: 'string', options: statusOptions }),
search: f.text({ label: 'Search' })
});One useFilters call gives you two outputs, used in two different places:
paramsis a plain object of the current values plus pagination ({ status, search, page, per_page }). Pass it to your API, and use it as your cache key.filtersis the same set resolved with a livevalueand change handlers, ready to render as whatever controls you like.
Both derive from the URL query string, the single source of truth. Change a filter and the URL changes; load a URL and the filters restore. The How it works page walks through that model in full, and it's worth reading before the rest.
What it is (and isn't)
It is a state manager for filters, built on nuqs for the URL layer. It handles serialization, typing, pagination, debounced and deferred commits, and a matching helper for route loaders.
It is not a data-fetching library or a component kit. You bring your own fetcher
(React Query, SWR, fetch) and your own controls (plain inputs, a design system,
shadcn/ui). That keeps it tiny, with zero runtime dependencies beyond the react
and nuqs peers, and unopinionated about how your app looks.
When to reach for it
A good fit when you have a filtered list or table and want:
- filters that live in the URL (shareable, refresh-safe, back-button friendly),
- one typed object to drive fetching and caching,
- consistent handling of pagination, debounced search, and "Apply" panels,
- the same values computed in a route loader for SSR prefetching.
If you only have one or two throwaway filters and don't care about the URL, plain
useState is fine. Everywhere else, this removes the boilerplate.
Where to go next
| If you want to… | Read |
|---|---|
| get something running | Installation, then Quickstart |
| understand the model | How it works |
| see every filter kind | Filter types |
| wire it up to your API | Fetching data |
| share setup across screens | Sharing setup |
| look up an option | API reference |