Zustand URL Query String Sync
raw JSON →zustand-querystring is a middleware for Zustand that provides declarative synchronization between parts of a Zustand store's state and the URL's query string. This enables persistent and shareable application states directly embedded in the browser's URL. The current stable version is 0.7.0, with ongoing development evidenced by recent frequent releases that include bug fixes and new features. Key differentiators include flexible state selection via the `select` option, support for multiple serialization formats (e.g., `plain`, `readable`, or custom), granular control over how `null` and `undefined` values are handled (`syncNull`, `syncUndefined`), and a powerful bidirectional `map` option for complex transformations between store state and URL representation. It supports both individual query parameters for each state key (`key: false`) or consolidating the entire state into a single query parameter, and offers prefixing for managing multiple stores on the same page.
Common errors
error Comma-separated arrays not restored from URL (e.g. `filters.gpus_.architecture=Blackwell,Ada` parsed as string) ↓
zustand-querystring to version 0.6.0 or higher, which includes a fix for this parsing issue. error Query parameters are double-encoded in URL (e.g., `?state=%2522hello%2522` instead of `?state=%22hello%22`) ↓
zustand-querystring version 0.3.1 or later. error Zustand state not showing up in the URL query string, or not restoring correctly from URL ↓
select function correctly returns true for all state fields you intend to sync. Also, check the key option: key: false uses individual parameters, while key: 'state' (or any string) consolidates state into a single parameter named 'state' (or your chosen string). error Null or undefined values for state properties are not reflected in the URL and disappear after refresh ↓
null or undefined values, set syncNull: true or syncUndefined: true (or both) in the middleware options: { syncNull: true, syncUndefined: true }. Warnings
breaking Version 0.1.0 introduced a breaking change by switching from a proprietary URL encoding format to standard `encodeURIComponent`/`decodeURIComponent`. Existing URLs encoded with pre-0.1.0 versions will no longer be correctly parsed. ↓
breaking Version 0.4.0 introduced breaking changes to the encoding scheme of the 'readable' format. If you were using the readable format prior to 0.4.0, URLs will no longer be correctly parsed. ↓
gotcha By default, `null` and `undefined` values are not synced to the URL. Setting a state property to `null` or `undefined` will effectively clear it from the URL and reset it to its initial state value in the store. ↓
gotcha Prior to v0.6.0, comma-separated arrays in the 'plain' format with dynamic keys were incorrectly restored as a single string instead of an array. For example, `filters.gpus_.architecture=Blackwell,Ada` would parse as `"Blackwell,Ada"`. ↓
breaking Changing the `key` option from `false` (default, individual query parameters) to a string value (e.g., `key: 'state'`, which consolidates all state into one parameter) will fundamentally change your URL structure. Existing URLs will become incompatible. ↓
Install
npm install zustand-querystring yarn add zustand-querystring pnpm add zustand-querystring Imports
- querystring wrong
const { querystring } = require('zustand-querystring');correctimport { querystring } from 'zustand-querystring'; - plain wrong
import { plain } from 'zustand-querystring';correctimport { plain } from 'zustand-querystring/format/plain'; - readable wrong
import { readable } from 'zustand-querystring/format/readable';correctimport * as format from 'zustand-querystring/format/readable';
Quickstart
import { create } from 'zustand';
import { querystring } from 'zustand-querystring';
interface MyStoreState {
search: string;
page: number;
setSearch: (search: string) => void;
setPage: (page: number) => void;
}
const useStore = create<MyStoreState>()(
querystring(
set => ({
search: '',
page: 1,
setSearch: search => set({ search }),
setPage: page => set({ page }),
}),
{
// Select which parts of the state to synchronize with the URL
select: () => ({ search: true, page: true }),
// Use individual query parameters like '?search=hello&page=2' (default)
key: false
},
),
);
// To demonstrate usage, you can update the state, which will reflect in the URL
// For example, calling this in a component:
// useStore.getState().setSearch('example-query');
// useStore.getState().setPage(5);
// This will update the URL to something like: ?search=example-query&page=5