Socket
Book a DemoInstallSign in
Socket

zustand-querystring

Package Overview
Dependencies
Maintainers
1
Versions
28
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

zustand-querystring

Zustand middleware for URL query string sync.

latest
Source
npmnpm
Version
0.5.0
Version published
Weekly downloads
1.2K
35.21%
Maintainers
1
Weekly downloads
 
Created
Source

zustand-querystring

Zustand middleware for URL query string sync.

npm install zustand-querystring

Usage

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

Options

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
})

select

Controls 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 },
  },
})

key

  • false (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
    

prefix

Adds 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 / syncUndefined

By default, null and undefined reset to initial state (removed from URL). Set to true to write them.

url

For SSR, pass the request URL:

querystring(store, { url: request.url, select: () => ({ search: true }) })

How State Syncs

  • On page load: URL → State
  • On state change: State → URL (via 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:

  • Objects: recursively diffed
  • Arrays, Dates: compared as whole values
  • Functions: never synced

Formats

Three built-in formats:

FormatExample Output
markedcount:5,tags@a,b~
plaincount=5&tags=a,b
jsoncount=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 })

Marked Format (default)

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',
});

Plain Format

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',
});

JSON Format

URL-encoded JSON. No configuration.

Custom Format

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 coercion

Examples

Search with reset

const 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 }) }
  )
);

Multiple stores with prefixes

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

Next.js SSR

// app/page.tsx
export default async function Page({ searchParams }) {
  // Store reads from URL on init
}

Exports

// 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';

Playground · GitHub

Keywords

zustand

FAQs

Package last updated on 14 Dec 2025

Did you know?

Socket

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.

Install

Related posts