npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tanstack-router-rest-search-serializer

v0.2.2

Published

REST API compliant search param serializer for TanStack Router

Readme

tanstack-router-rest-search-serializer

CI npm version License: MIT

Note: This is an unofficial community package, not affiliated with TanStack.

A REST API compliant search param serializer for TanStack Router.

Instead of using JSON.stringify / JSON.parse, this serializer uses the URLSearchParams format that conforms to REST API specifications.

Why this package?

TanStack Router serializes search parameters as JSON by default. While this works well for internal application state, it causes issues when integrating with external systems:

  • External system compatibility: URLs like ?userCode=%22123%22 (JSON-encoded string) are difficult for external tools and APIs to generate
  • Type mismatches: Backend APIs expect standard query string formats, not JSON-encoded values
  • Non-standard format: Deviates from the industry-standard application/x-www-form-urlencoded format

This package provides a REST API compliant serializer that uses the standard URLSearchParams format, making URLs human-readable and compatible with any HTTP client or external system.

For more details, see the blog post (Japanese).

Table of Contents

Features

  • REST API compliant URL format (no JSON encoding)
  • Supports comma-separated arrays (?ids=1,2,3)
  • Supports Rails-style nested objects (?user[name]=john)
  • Supports PHP-style arrays (?ids[]=1&ids[]=2)
  • Automatic boolean string conversion ("true"true)
  • Customizable feature options
  • TypeScript support
  • Zero dependencies (except peer dependency on zod for helpers)

Installation

npm install tanstack-router-rest-search-serializer
# or
pnpm add tanstack-router-rest-search-serializer
# or
yarn add tanstack-router-rest-search-serializer

Quick Start

import {
  parseSearchParams,
  stringifySearchParams,
} from 'tanstack-router-rest-search-serializer'

// Set globally in createRouter
const router = createRouter({
  routeTree,
  parseSearch: parseSearchParams,
  stringifySearch: stringifySearchParams,
})

Differences from TanStack Router Default

By default, TanStack Router encodes search parameters as JSON.

Parse (URL → Object):

| URL | TanStack Router Default | This Serializer | | ------------------------------ | ---------------------------- | -------------------------------------------- | | ?foo=bar | { foo: 'bar' } | { foo: 'bar' } | | ?count=123 | { count: 123 } (number) | { count: '123' } (string) | | ?code=%22123%22 ("123") | { code: '123' } (3 chars) | { code: '"123"' } (5 chars, with quotes) | | ?active=true | { active: true } (boolean) | { active: true } (boolean) | | ?ids=%5B%221%22%2C%222%22%5D | { ids: ['1', '2'] } | { ids: ['["1"', '"2"]'] } (split by comma) | | ?ids=1,2 | { ids: '1,2' } (string) | { ids: ['1', '2'] } | | ?foo= | { foo: '' } (empty string) | { foo: '' } (empty string) |

Stringify (Object → URL):

| Data | TanStack Router Default | This Serializer | | ---------------------------- | --------------------------------------------------------- | ------------------------------------------ | | { foo: 'bar' } | ?foo=bar | ?foo=bar | | { count: 123 } | ?count=123 | ?count=123 | | { code: '123' } | ?code=%22123%22 ("123") | ?code=123 | | { active: true } | ?active=true | ?active=true | | { ids: ['1', '2'] } | ?ids=%5B%221%22%2C%222%22%5D (["1","2"]) | ?ids=1%2C2 (1,2) | | { user: { name: 'john' } } | ?user=%7B%22name%22%3A%22john%22%7D ({"name":"john"}) | ?user%5Bname%5D=john (user[name]=john) |

Key Differences:

  • Numbers: TanStack Router preserves type, this serializer converts to string
  • Strings: TanStack Router JSON encodes, this serializer keeps as-is
  • Arrays: TanStack Router uses JSON format, this serializer uses comma-separated
  • Nested Objects: TanStack Router uses JSON format, this serializer uses Rails-style
  • Empty Strings: Both preserve empty string '' as-is
    • Comma-separated format cannot distinguish between empty array [] and empty string ''
    • Empty values like ?ids= are parsed as empty string ''

Supported Formats

| Format | Query String | Parse Result | | ---------------------- | ------------------------ | -------------------------------- | | Standard | ?foo=bar | { foo: 'bar' } | | Boolean strings | ?active=true | { active: true } | | Comma-separated arrays | ?ids=1,2,3 | { ids: ['1', '2', '3'] } | | PHP-style arrays | ?ids[]=1&ids[]=2 | { ids: ['1', '2'] } | | Duplicate key arrays | ?ids=1&ids=2 | { ids: ['1', '2'] } | | Nested objects | ?user[name]=john | { user: { name: 'john' } } | | Objects in arrays | ?items[0][name]=apple | { items: [{ name: 'apple' }] } | | Numeric index arrays | ?items[0]=a&items[1]=b | { items: ['a', 'b'] } |

Custom Serializer

You can create a custom serializer by selecting specific features.

import {
  createSerializer,
  SIMPLE_FEATURES,
} from 'tanstack-router-rest-search-serializer'

// Simple features only (standard format + boolean conversion)
const simple = createSerializer(SIMPLE_FEATURES)

// Select individual features
const custom = createSerializer({
  commaSeparatedArrays: true,
  booleanStrings: true,
  nestedObjects: false,
  phpArrays: false,
  duplicateKeyArrays: true,
  numericIndexArrays: false,
})

Feature Options

| Option | Default | Description | | ---------------------- | ------- | -------------------------------------------------------------- | | commaSeparatedArrays | true | Parse comma-separated arrays | | booleanStrings | true | Convert "true" / "false" to boolean | | nestedObjects | true | Rails-style nested objects | | phpArrays | true | PHP-style arrays ids[]=1 | | duplicateKeyArrays | true | Duplicate key arrays ids=1&ids=2 | | numericIndexArrays | true | Numeric index arrays items[0]=a | | jsonFallback | false | Parse JSON-encoded values for backward compatibility |

JSON Fallback

Enable jsonFallback to parse JSON-encoded values from TanStack Router's default format. This is useful for backward compatibility when migrating from JSON to REST format.

const { parseSearchParams, stringifySearchParams } = createSerializer({
  jsonFallback: true,
})

// Parses TanStack Router default JSON format:
// ?ids=%5B%221%22%2C%222%22%5D (["1","2"]) → { ids: ['1', '2'] }
// ?code=%22123%22 ("123") → { code: '123' }
// ?user=%7B%22name%22%3A%22john%22%7D ({"name":"john"}) → { user: { name: 'john' } }

Presets

  • FULL_FEATURES: All features enabled except jsonFallback
  • SIMPLE_FEATURES: Standard format + boolean conversion only

Zod Helpers

When using TanStack Router's validateSearch with Zod, these helpers solve format compatibility issues.

Why are these helpers needed?

The comma-separated format cannot distinguish between an empty array [] and an empty string ''. When parsing ?ids=, this serializer returns '' (empty string), but Zod's z.array() expects an array. The commaSeparatedArray helper handles this conversion.

commaSeparatedArray

Handles empty string ''[] conversion for array fields:

import { z } from 'zod'
import { commaSeparatedArray } from 'tanstack-router-rest-search-serializer/zod-helpers'

const searchSchema = z.object({
  ids: commaSeparatedArray(z.string()).optional(),
})

// ?ids=1,2,3 → { ids: ['1', '2', '3'] }
// ?ids=      → { ids: [] }

joinCommaArray

Joins arrays back into comma-separated strings. Use when you want certain parameters to remain as strings containing commas:

import { z } from 'zod'
import { joinCommaArray } from 'tanstack-router-rest-search-serializer/zod-helpers'

const searchSchema = z.object({
  // Keep as string containing commas
  freeText: joinCommaArray(z.string()).optional(),
})

// ?freeText=a,b,c → { freeText: 'a,b,c' } (string, not array)

Usage with Route Definition

Example of using with TanStack Router's route definition and Zod validation:

import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
import {
  parseSearchParams,
  stringifySearchParams,
} from 'tanstack-router-rest-search-serializer'
import { commaSeparatedArray } from 'tanstack-router-rest-search-serializer/zod-helpers'

const searchSchema = z.object({
  q: z.string().optional(),
  page: z.coerce.number().default(1),
  tags: commaSeparatedArray(z.string()).optional(),
  active: z.boolean().optional(),
})

export const Route = createFileRoute('/search')({
  validateSearch: searchSchema,
})

// URL: /search?q=hello&page=2&tags=react,typescript&active=true
// Result: { q: 'hello', page: 2, tags: ['react', 'typescript'], active: true }

Limitations

  • Type coercion: All values are parsed as strings. Use z.coerce.number() for numeric values.
  • Empty arrays: Cannot distinguish between [] and ''. Use commaSeparatedArray helper.
  • Commas in values: Values containing commas will be split into arrays. Use joinCommaArray helper if needed.
  • Deep nesting: While supported, deeply nested structures may result in long URLs.

TypeScript

This package is written in TypeScript and includes type definitions.

Requirements

  • TanStack Router v1.x
  • TypeScript 5.0+ (if using TypeScript)

Related

License

MIT

Contributing

Issues and pull requests are welcome on GitHub.