Arrays, Objects, and Dates in Search Params

Purpose and Scope

This page explains how to model non-trivial URL state in TanStack Router search params. Search params are the part of a URL after the question mark, but Router treats them as more than a flat string map. The Router documentation frames them as application state that should be shareable, bookmarkable, refresh-safe, and type-safe. That matters for product pages, dashboards, analytics tools, and search UIs where filters are often arrays, grouped objects, ranges, booleans, and sometimes dates rather than only strings.

Sources: docs/router/guide/search-params.md, docs/router/how-to/arrays-objects-dates-search-params.md

TanStack Router’s default search-param model is JSON-first. Instead of requiring every value to be manually encoded into a string, Router parses the URL search string into structured JSON and serializes structured search objects back to the URL. The practical result is that a route can validate and consume arrays, nested objects, numbers, and booleans through route APIs such as validateSearch and Route.useSearch(). The how-to page builds on that model with examples for tags, filters, date ranges, pagination, and nested sorting state.

Sources: docs/router/guide/search-params.md, docs/router/how-to/arrays-objects-dates-search-params.md

Relevant Source Files

  • docs/router/how-to/arrays-objects-dates-search-params.md - Task-oriented guide for arrays, objects, dates, nested search structures, route validation, and navigation updates.
  • docs/router/guide/search-params.md - Conceptual guide that explains why Router does not rely only on URLSearchParams, why search params are URL-backed application state, and how JSON-first parsing enables structured values.
  • docs/router/guide/custom-search-param-serialization.md - Serialization guide for the default JSON.stringify and JSON.parse behavior and for replacing that behavior with parseSearchWith and stringifySearchWith when the app needs a different URL format.

Core Model: JSON-First URL State

The main search params guide starts from a developer-experience problem: platform URLSearchParams assumes string-oriented, mostly flat data, while real application state frequently uses richer shapes. A paginated product list may need a page number, selected categories, a price range, a set of sort fields, and a nested filter object. If every field is converted by hand, validation, navigation, and rendering code become tightly coupled to a chosen string format instead of to the application’s actual state shape.

Sources: docs/router/guide/search-params.md

Router’s JSON-first parser is intended to remove that coupling. A search object can be passed to a Link or navigation call, then read back as structured data after parsing. This is especially important in frameworks where object identity, immutability, and structural sharing affect rendering behavior. The guide explicitly calls out that repeated stringify-and-parse cycles can create new object references and lead to unwanted performance behavior if the router does not manage parsed search state carefully over its lifetime.

Sources: docs/router/guide/search-params.md

Use this model when a value belongs in the URL because a user should be able to share it, bookmark it, open it in a new tab, or preserve it across refresh and browser history navigation. Do not put every component state value into search params automatically. Search params are best for externally meaningful state: selected filters, tabs, pagination, sort order, view mode, and other values that define what the user is looking at rather than incidental UI details.

Arrays: Filters, Tags, Ranges, and Multi-Select State

Arrays are the most common step beyond primitive search params. The complex-search how-to shows array use cases such as categories, tags, and a two-number priceRange. The key pattern is to validate the array shape at the route boundary, give the route safe defaults where appropriate, then read the typed result through the route’s search hook. That keeps rendering code from guessing whether a value is a string, a missing value, or an array with the right element type.

Sources: docs/router/how-to/arrays-objects-dates-search-params.md

import { createFileRoute, Link } from '@tanstack/react-router'
import { z } from 'zod'
 
const searchSchema = z.object({
  categories: z.array(z.string()).default([]),
  tags: z.array(z.string()).optional(),
  priceRange: z.array(z.number()).length(2).optional(),
})
 
export const Route = createFileRoute('/products')({
  validateSearch: searchSchema,
  component: ProductsComponent,
})
 
function ProductsComponent() {
  const { categories, tags, priceRange } = Route.useSearch()
  return <h2>Active Categories: {categories.join(', ')}</h2>
}

Navigation should usually update arrays immutably. The how-to demonstrates functional search updates where the callback receives previous search state and returns the next state. That lets a filter control add one category without dropping unrelated search params, remove one element by filtering, replace an entire selection, or clear an array by returning an empty list. This style is useful because array search params commonly interact with other URL state, such as pagination or sorting.

Sources: docs/router/how-to/arrays-objects-dates-search-params.md

<Link
  to="/products"
  search={(prev) => ({
    ...prev,
    categories: [...(prev.categories || []), 'electronics'],
  })}
/>
 
<Link
  to="/products"
  search={(prev) => ({
    ...prev,
    categories: prev.categories?.filter((cat) => cat !== 'electronics') || [],
  })}
/>

For advanced arrays, validate the element shape as well as the container. The how-to includes arrays of filter objects with field, operator, and value, arrays constrained by maximum length, and arrays transformed to a supported subset of sort fields. Those examples show the recommended direction: complex URL state should be explicit and constrained, not treated as an arbitrary bag of decoded JSON. Validation is what turns shareable URL state into safe application input.

Objects, Dates, and Nested Structures

Objects are useful when several search params form one conceptual unit. The how-to’s dashboard example groups layout configuration under view, including layout type, column count, and a showDetails flag. The same pattern applies to filter groups, table state, or chart configuration. A nested object keeps related fields together in the route contract and helps components read one cohesive value rather than coordinating many loose top-level keys.

Sources: docs/router/how-to/arrays-objects-dates-search-params.md

Dates require an extra design decision because JavaScript Date objects are not JSON values in the same way strings, numbers, booleans, arrays, and plain objects are. The how-to introduces date ranges as a common complex search shape, but the broader serialization guide is the important companion: the serializer and parser must round-trip the value without losing information. In practice, applications should choose a durable URL representation such as ISO strings, then transform or validate those strings into dates at the route boundary when component code needs Date instances.

Sources: docs/router/how-to/arrays-objects-dates-search-params.md, docs/router/guide/custom-search-param-serialization.md

Nested pagination and sorting show how arrays and objects combine. A single pagination object can hold page, size, and a nested sort object containing field and direction. This is easier to evolve than independent top-level keys when the UI treats pagination as one feature. The tradeoff is that the URL may become less human-readable as nested objects are encoded, so complex shapes should be reserved for state that benefits from grouping, validation, and type-safe consumption.

Serialization Choices and Round-Trip Safety

By default, Router parses and serializes URL search params using JSON.parse and JSON.stringify, with escaping and unescaping around the URL search string. The custom serialization guide shows the default behavior with parseSearchWith(JSON.parse) and stringifySearchWith(JSON.stringify) in the router configuration. This matters for complex values because the route’s search object is only reliable if serialization and deserialization return the same logical data shape.

Sources: docs/router/guide/custom-search-param-serialization.md

import {
  createRouter,
  parseSearchWith,
  stringifySearchWith,
} from '@tanstack/react-router'
 
const router = createRouter({
  parseSearch: parseSearchWith(JSON.parse),
  stringifySearch: stringifySearchWith(JSON.stringify),
})

Custom serialization is appropriate when the default escaped JSON format is not ideal for the application. The guide names base64 encoding and purpose-built libraries such as query-string, JSURL2, and Zipson as examples of alternate formats. The key requirement is idempotency: after a value is serialized into the URL and parsed back, the application should receive equivalent information. If a serializer cannot represent nested objects or arrays correctly, complex search params will silently degrade.

Sources: docs/router/guide/custom-search-param-serialization.md

Implementation Checklist

Start by deciding which state is URL-worthy. If the value changes the meaning of the page, such as selected filters or a date range, model it in route search. Next, define a validation schema with defaults for values that components should always be able to read, such as an empty category list. Then use route search APIs for reads and typed link or navigation options for writes, preferably with functional updates when the change should preserve unrelated search fields.

Sources: docs/router/how-to/arrays-objects-dates-search-params.md, docs/router/guide/search-params.md

Keep the serialized representation in mind when adding dates or deeply nested objects. For dates, prefer a stable string form in the URL and convert at the edge. For arrays of objects, constrain the allowed operators, fields, and element count so copied URLs cannot create excessive or unsupported state. For objects, use defaults or prefault-style schema behavior when nested groups are optional but components expect individual fields inside them.

Sources: docs/router/how-to/arrays-objects-dates-search-params.md

Next Steps

Read the main Search Params guide before designing a route-wide search contract, then use the complex-search how-to for concrete array and object patterns. If the URL format must be shorter, more compatible, or compatible with an existing query-string convention, continue to the Custom Search Param Serialization guide and configure parseSearch and stringifySearch at router creation time. Related pages: search-params, custom-search-param-serialization, type-safety, and navigation-and-links.