
Research
/Security News
Fake imToken Chrome Extension Steals Seed Phrases via Phishing Redirects
Mixed-script homoglyphs and a lookalike domain mimic imToken’s import flow to capture mnemonics and private keys.
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&tags=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, repeated keys for arrays.
import { createFormat } from 'zustand-querystring/format/plain';
const format = createFormat({
entrySeparator: ',', // between entries in namespaced mode
nestingSeparator: '.', // for nested keys
arraySeparator: 'repeat', // 'repeat' for ?tags=a&tags=b, or ',' for ?tags=a,b
escapeChar: '_',
nullString: 'null',
undefinedString: 'undefined',
infinityString: 'Infinity', // string representation of Infinity
negativeInfinityString: '-Infinity',
nanString: 'NaN',
});
Note on
arraySeparator: ',': With comma-separated arrays and dynamic keys (e.g.,initialState: { filters: {} }), a single array value likeos=CentOSis indistinguishable from a scalar string. Use'repeat'for dynamic keys, or normalize withArray.isArray(val) ? val : [val].
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,526 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.

Research
/Security News
Mixed-script homoglyphs and a lookalike domain mimic imToken’s import flow to capture mnemonics and private keys.

Security News
Latio’s 2026 report recognizes Socket as a Supply Chain Innovator and highlights our work in 0-day malware detection, SCA, and auto-patching.

Company News
Join Socket for live demos, rooftop happy hours, and one-on-one meetings during BSidesSF and RSA 2026 in San Francisco.