
Security News
CVE Volume Surges Past 48,000 in 2025 as WordPress Plugin Ecosystem Drives Growth
CVE disclosures hit a record 48,185 in 2025, driven largely by vulnerabilities in third-party WordPress plugins.
zustand-querystring
Advanced tools
Zustand middleware for URL query string sync.
npm install zustand-querystring
import { create } from 'zustand';
import { querystring } from 'zustand-querystring';
const useStore = create(
querystring(
(set) => ({
search: '',
page: 1,
setSearch: (search) => set({ search }),
setPage: (page) => set({ page }),
}),
{
select: () => ({ search: true, page: true }),
}
)
);
// URL: ?search=hello&page=2
querystring(storeCreator, {
select: undefined, // which fields to sync
key: false, // false | 'state'
prefix: '', // prefix for URL params
format: marked, // serialization format
syncNull: false, // sync null values
syncUndefined: false, // sync undefined values
url: undefined, // request URL for SSR
})
selectControls which state fields sync to URL. Receives pathname, returns object with true for fields to sync.
// All fields
select: () => ({ search: true, page: true, filters: true })
// Route-based
select: (pathname) => ({
search: true,
filters: pathname.startsWith('/products'),
adminSettings: pathname.startsWith('/admin'),
})
// Nested fields
select: () => ({
user: {
name: true,
settings: { theme: true },
},
})
keyfalse (default): Each field becomes a separate URL param
?search=hello&page=2&filters.sort=name
'state' (or any string): All state in one param
?state=search%3Dhello%2Cpage%3A2
prefixAdds prefix to all params. Use when multiple stores share URL.
querystring(storeA, { prefix: 'a_', select: () => ({ search: true }) })
querystring(storeB, { prefix: 'b_', select: () => ({ filter: true }) })
// URL: ?a_search=hello&b_filter=active
syncNull / syncUndefinedBy default, null and undefined reset to initial state (removed from URL). Set to true to write them.
urlFor SSR, pass the request URL:
querystring(store, { url: request.url, select: () => ({ search: true }) })
replaceState)Only values different from initial state are written to URL:
// Initial: { search: '', page: 1, sort: 'date' }
// Current: { search: 'hello', page: 1, sort: 'name' }
// URL: ?search=hello&sort=name
// (page omitted - matches initial)
Type handling:
Three built-in formats:
| Format | Example Output |
|---|---|
marked | count:5,tags@a,b~ |
plain | count=5&tags=a,b |
json | count=5&tags=%5B%22a%22%5D |
import { marked } from 'zustand-querystring/format/marked';
import { plain } from 'zustand-querystring/format/plain';
import { json } from 'zustand-querystring/format/json';
querystring(store, { format: plain })
Type markers: : primitive, = string, @ array, . object
Delimiters: , separator, ~ terminator, _ escape
import { createFormat } from 'zustand-querystring/format/marked';
const format = createFormat({
typeObject: '.',
typeArray: '@',
typeString: '=',
typePrimitive: ':',
separator: ',',
terminator: '~',
escapeChar: '_',
datePrefix: 'D',
});
Dot notation for nesting, comma-separated arrays.
import { createFormat } from 'zustand-querystring/format/plain';
const format = createFormat({
entrySeparator: ',', // between entries in namespaced mode
nestingSeparator: '.', // for nested keys
arraySeparator: ',', // or 'repeat' for ?tags=a&tags=b&tags=c
escapeChar: '_',
nullString: 'null',
undefinedString: 'undefined',
});
URL-encoded JSON. No configuration.
Implement QueryStringFormat:
import type { QueryStringFormat, QueryStringParams, ParseContext } from 'zustand-querystring';
const myFormat: QueryStringFormat = {
// For key: 'state' (namespaced mode)
stringify(state: object): string {
return encodeURIComponent(JSON.stringify(state));
},
parse(value: string, ctx?: ParseContext): object {
return JSON.parse(decodeURIComponent(value));
},
// For key: false (standalone mode)
stringifyStandalone(state: object): QueryStringParams {
const result: QueryStringParams = {};
for (const [key, value] of Object.entries(state)) {
result[key] = [encodeURIComponent(JSON.stringify(value))];
}
return result;
},
parseStandalone(params: QueryStringParams, ctx: ParseContext): object {
const result: Record<string, unknown> = {};
for (const [key, values] of Object.entries(params)) {
result[key] = JSON.parse(decodeURIComponent(values[0]));
}
return result;
},
};
querystring(store, { format: myFormat })
Types:
QueryStringParams = Record<string, string[]> (values always arrays)ctx.initialState available for type coercionconst useStore = create(
querystring(
(set) => ({
query: '',
page: 1,
setQuery: (query) => set({ query, page: 1 }), // reset page on new query
setPage: (page) => set({ page }),
}),
{ select: () => ({ query: true, page: true }) }
)
);
const useFilters = create(
querystring(filtersStore, {
prefix: 'f_',
select: () => ({ category: true, price: true }),
})
);
const usePagination = create(
querystring(paginationStore, {
prefix: 'p_',
select: () => ({ page: true, limit: true }),
})
);
// URL: ?f_category=shoes&f_price=100&p_page=2&p_limit=20
// app/page.tsx
export default async function Page({ searchParams }) {
// Store reads from URL on init
}
// Middleware
import { querystring } from 'zustand-querystring';
// Formats
import { marked, createFormat } from 'zustand-querystring/format/marked';
import { plain, createFormat } from 'zustand-querystring/format/plain';
import { json } from 'zustand-querystring/format/json';
// Types
import type {
QueryStringOptions,
QueryStringFormat,
QueryStringParams,
ParseContext,
} from 'zustand-querystring';
FAQs
Zustand middleware for URL query string sync.
The npm package zustand-querystring receives a total of 1,022 weekly downloads. As such, zustand-querystring popularity was classified as popular.
We found that zustand-querystring demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
CVE disclosures hit a record 48,185 in 2025, driven largely by vulnerabilities in third-party WordPress plugins.

Security News
Socket CEO Feross Aboukhadijeh joins Insecure Agents to discuss CVE remediation and why supply chain attacks require a different security approach.

Security News
Tailwind Labs laid off 75% of its engineering team after revenue dropped 80%, as LLMs redirect traffic away from documentation where developers discover paid products.