Skip to main content

Parser Architecture

Parsers in nuqs are the foundation of type-safe URL state synchronization. They define how values are converted between URL query strings (always strings) and typed application state.

Core Interfaces

SingleParser

The SingleParser interface handles single-value query parameters:
export type SingleParser<T> = {
  type?: 'single'
  
  /**
   * Convert a query string value into a state value.
   *
   * If the string value does not represent a valid state value,
   * the parser should return `null`. Throwing an error is also supported.
   */
  parse: (value: string) => T | null

  /**
   * Render the state value into a query string value.
   */
  serialize?: (value: T) => string

  /**
   * Check if two state values are equal.
   *
   * This is used when using the `clearOnDefault` value, to compare the default
   * value with the set value.
   *
   * It makes sense to provide this function when the state value is an object
   * or an array, as the default referential equality check will not work.
   */
  eq?: (a: T, b: T) => boolean
}
From: packages/nuqs/src/parsers.ts:7-32
type
'single'
Parser type identifier. Defaults to 'single' if not specified.
parse
(value: string) => T | null
required
Converts a URL query string value to the typed state value.Must return null for invalid inputs rather than throwing errors. This ensures graceful degradation when URLs contain unexpected values.
serialize
(value: T) => string
Converts the typed state value back to a URL query string.Must be lossless - parsing a serialized value should return the original value.Defaults to String if not provided.
eq
(a: T, b: T) => boolean
Custom equality function for comparing state values.Used with clearOnDefault to determine when to remove the query parameter from the URL.Defaults to referential equality (a === b) if not provided. Essential for objects and arrays.

MultiParser

The MultiParser interface handles array-based query parameters (e.g., ?tag=react&tag=next):
export type MultiParser<T> = {
  type: 'multi'
  parse: (value: ReadonlyArray<string>) => T | null
  serialize?: (value: T) => Array<string>
  eq?: (a: T, b: T) => boolean
}
From: packages/nuqs/src/parsers.ts:34-39
type
'multi'
required
Must be set to 'multi' to handle multiple values for the same key.
parse
(value: ReadonlyArray<string>) => T | null
required
Parses an array of query string values. Each value comes from a repeated parameter (e.g., ?id=1&id=2&id=3).
serialize
(value: T) => Array<string>
Serializes the state value into an array of query string values.
eq
(a: T, b: T) => boolean
Custom equality function for comparing state values.

Builder Pattern

All parsers created with createParser implement the builder pattern for configuration:

SingleParserBuilder

export type SingleParserBuilder<T> = Required<SingleParser<T>> &
  Options & {
    /**
     * Set history type, shallow routing and scroll restoration options
     * at the hook declaration level.
     */
    withOptions<This>(this: This, options: Options): This

    /**
     * Specifying a default value makes the hook state non-nullable when the
     * query is missing from the URL: the default value is returned instead
     * of `null`.
     */
    withDefault(
      this: SingleParserBuilder<T>,
      defaultValue: NonNullable<T>
    ): Omit<SingleParserBuilder<T>, 'parseServerSide'> & {
      readonly defaultValue: NonNullable<T>
      parseServerSide(value: string | string[] | undefined): NonNullable<T>
    }

    /**
     * Use the parser in Server Components
     * @deprecated prefer using loaders instead
     */
    parseServerSide(value: string | string[] | undefined): T | null
  }
From: packages/nuqs/src/parsers.ts:52-123

MultiParserBuilder

export type MultiParserBuilder<T> = Required<MultiParser<T>> &
  Options & {
    withOptions<This>(this: This, options: Options): This
    withDefault(
      this: MultiParserBuilder<T>,
      defaultValue: NonNullable<T>
    ): Omit<MultiParserBuilder<T>, 'parseServerSide'> & {
      readonly defaultValue: NonNullable<T>
      parseServerSide(value: string | string[] | undefined): NonNullable<T>
    }
    parseServerSide(value: string | string[] | undefined): T | null
  }
From: packages/nuqs/src/parsers.ts:125-144

Type Inference

Extract TypeScript types from parsers using the inferParserType helper:
import { type inferParserType } from 'nuqs'

const intNullable = parseAsInteger
const intNonNull = parseAsInteger.withDefault(0)

type A = inferParserType<typeof intNullable> // number | null
type B = inferParserType<typeof intNonNull>  // number

// Works with parser objects too
const parsers = {
  a: parseAsInteger,
  b: parseAsBoolean.withDefault(false)
}

type State = inferParserType<typeof parsers>
// { a: number | null, b: boolean }
export type inferParserType<Input> =
  Input extends GenericParserBuilder<any>
    ? inferSingleParserType<Input>
    : Input extends Record<string, GenericParserBuilder<any>>
      ? inferParserRecordType<Input>
      : never
From: packages/nuqs/src/parsers.ts:583-588

Design Principles

1. Always Return Null for Invalid Input

Parsers should gracefully handle invalid input by returning null, never throwing errors:
// ✅ Good
const parseAsInteger = createParser({
  parse: v => {
    const int = parseInt(v)
    return int == int ? int : null // NaN check
  },
  serialize: v => '' + Math.round(v)
})

// ❌ Bad - throws errors
const parseAsInteger = createParser({
  parse: v => {
    if (!/^\d+$/.test(v)) {
      throw new Error('Invalid integer')
    }
    return parseInt(v)
  },
  serialize: String
})

2. Lossless Serialization

Serializers must be lossless to prevent data corruption on page reload:
// ❌ Bad - loses precision
const parseAsFloat = createParser({
  parse: parseFloat,
  serialize: v => v.toFixed(2) // Rounds!
})

// ✅ Good - preserves precision
const parseAsFloat = createParser({
  parse: v => {
    const float = parseFloat(v)
    return float == float ? float : null
  },
  serialize: String // No rounding
})
From: packages/nuqs/src/parsers.ts:262-268

3. Provide Custom Equality for Objects

When parsing objects or arrays, provide a custom eq function:
const parseAsJson = createParser({
  parse: query => {
    try {
      return JSON.parse(query)
    } catch {
      return null
    }
  },
  serialize: value => JSON.stringify(value),
  eq(a, b) {
    // Check referential equality first for performance
    return a === b || JSON.stringify(a) === JSON.stringify(b)
  }
})
From: packages/nuqs/src/parsers.ts:452-455

Next Steps

Build docs developers (and LLMs) love