Shareable search and filter state

Search architecture

TL;DR
  • Search state was represented in too many places at once.
  • I needed URL parameters, form state, display units, and API filters to share one contract.
  • I built tolerant parsing, canonical serialization, and unit conversion at the boundary.
  • The result was predictable, shareable search state that could survive refreshes and edits.

Search state is easy to scatter. A filter can exist in the URL, the form, the API request, and the visible label all at the same time.

Where it started

Filters needed to be readable in the UI, valid in URLs, and correct for the API. Without a shared contract, each layer could interpret the same value differently.

What needed to change

The search contract defines how filters are parsed, normalized, serialized, and sent to the API. Invalid input can be tolerated without turning into broken state.

It also keeps display units and API units from leaking into each other.

What I built

URL parameters are parsed through a tolerant layer, converted into canonical state, and serialized back consistently. Unit conversion happens in one place before the request leaves the interface.

What changed

Search URLs become shareable and predictable. Users can refresh, copy links, or change filters without the interface inventing a new state format each time.

What I would watch

Canonical state needs discipline. Every new filter has to join the contract instead of sneaking in through local component state.