Skip to main content

Overview

The createSerializer function creates a type-safe serializer for generating URLs with search parameters. It’s useful for creating links, redirects, and programmatic navigation with properly formatted query strings.

Function Signature

function createSerializer<
  Parsers extends ParserMap,
  BaseType extends Base = Base,
  Return = string
>(
  parsers: Parsers,
  options?: CreateSerializerOptions<Parsers>
): SerializeFunction<Parsers, BaseType, Return>

Parameters

parsers
ParserMap
required
An object mapping search param keys to their parser configurations.
const parsers = {
  q: parseAsString,
  page: parseAsInteger,
  category: parseAsString
}
options
CreateSerializerOptions<Parsers>
Optional configuration object.

Return Value

Returns a SerializeFunction with two overloads:
type SerializeFunction<
  Parsers extends ParserMap,
  BaseType extends Base = Base,
  Return = string
> = {
  // Generate query string from values
  (values: Partial<Nullable<inferParserType<Parsers>>>): Return
  
  // Append/amend query string to base URL
  (
    base: BaseType,
    values: Partial<Nullable<inferParserType<Parsers>>> | null
  ): Return
}

Base Types

type Base = string | URLSearchParams | URL

Nullable Type Helper

type Nullable<T> = {
  [K in keyof T]: T[K] | null
}
Values can be set to null to remove them from the URL.

Type Definitions

ParserMap

type ParserMap = Record<string, ParserWithOptionalDefault<any>>

type ParserWithOptionalDefault<T> = GenericParserBuilder<T> & {
  defaultValue?: T
}

CreateSerializerOptions

type CreateSerializerOptions<Parsers extends ParserMap> = Pick<
  Options,
  'clearOnDefault'
> & {
  urlKeys?: UrlKeys<Parsers>
  processUrlSearchParams?: (searchParams: URLSearchParams) => URLSearchParams
}

inferParserType

TypeScript automatically infers types based on your parsers:
const serialize = createSerializer({
  count: parseAsInteger,
  active: parseAsBoolean,
  tags: parseAsArrayOf(parseAsString)
})

// TypeScript knows the shape of the values object
serialize({
  count: 42,        // number | null
  active: true,     // boolean | null
  tags: ['a', 'b']  // string[] | null
})

Examples

Generate Query String

import { createSerializer, parseAsString, parseAsInteger } from 'nuqs/server'

const serialize = createSerializer({
  q: parseAsString,
  page: parseAsInteger,
  category: parseAsString
})

serialize({ q: 'laptop', page: 2 })
// "?q=laptop&page=2"

serialize({ category: 'electronics' })
// "?category=electronics"

Append to Base URL

const serialize = createSerializer({
  q: parseAsString,
  page: parseAsInteger
})

// Append to a path
serialize('/search', { q: 'laptop', page: 2 })
// "/search?q=laptop&page=2"

// Amend existing query string
serialize('/search?sort=price', { q: 'laptop' })
// "/search?sort=price&q=laptop"

// Use with URL object
const url = new URL('https://example.com/search')
serialize(url, { q: 'laptop' })
// "https://example.com/search?q=laptop"

// Use with URLSearchParams
const params = new URLSearchParams('?sort=price')
serialize(params, { q: 'laptop' })
// "?sort=price&q=laptop"

Remove Parameters with null

const serialize = createSerializer({
  q: parseAsString,
  page: parseAsInteger,
  category: parseAsString
})

// Remove specific parameters
serialize('/search?q=laptop&page=2&category=electronics', {
  category: null  // Remove category
})
// "/search?q=laptop&page=2"

// Remove all parameters by passing null as second argument
serialize('/search?q=laptop&page=2', null)
// "/search"

Default Value Behavior

const serialize = createSerializer({
  page: parseAsInteger.withDefault(1),
  limit: parseAsInteger.withDefault(20)
})

serialize({ page: 1, limit: 20 })
// "" (both values are defaults, so omitted)

serialize({ page: 2, limit: 20 })
// "?page=2" (limit omitted because it's the default)

serialize({ page: 1, limit: 50 })
// "?limit=50" (page omitted because it's the default)

Disable clearOnDefault

const serialize = createSerializer(
  {
    page: parseAsInteger.withDefault(1)
  },
  { clearOnDefault: false }
)

serialize({ page: 1 })
// "?page=1" (included even though it's the default)

URL Keys Mapping

const serialize = createSerializer(
  {
    searchQuery: parseAsString,
    pageNumber: parseAsInteger,
    itemsPerPage: parseAsInteger
  },
  {
    urlKeys: {
      searchQuery: 'q',
      pageNumber: 'page',
      itemsPerPage: 'limit'
    }
  }
)

serialize({
  searchQuery: 'laptop',
  pageNumber: 2,
  itemsPerPage: 50
})
// "?q=laptop&page=2&limit=50"

Post-Processing

const serialize = createSerializer(
  {
    q: parseAsString,
    page: parseAsInteger
  },
  {
    processUrlSearchParams: (params) => {
      // Add analytics parameters
      params.set('utm_source', 'app')
      params.set('utm_medium', 'link')
      
      // Sort parameters alphabetically
      params.sort()
      
      return params
    }
  }
)

serialize({ q: 'laptop', page: 2 })
// "?page=2&q=laptop&utm_medium=link&utm_source=app"
import Link from 'next/link'
import { createSerializer, parseAsString, parseAsInteger } from 'nuqs/server'

const serialize = createSerializer({
  q: parseAsString,
  page: parseAsInteger,
  category: parseAsString
})

export function ProductLink({ category }: { category: string }) {
  return (
    <Link href={serialize('/products', { category, page: 1 })}>
      View {category}
    </Link>
  )
}

export function PaginationLink({ page }: { page: number }) {
  return (
    <Link href={serialize({ page })}>
      Page {page}
    </Link>
  )
}

Server-Side Redirects (Next.js)

import { redirect } from 'next/navigation'
import { createSerializer, parseAsString } from 'nuqs/server'

const serialize = createSerializer({
  q: parseAsString
})

export async function searchAction(formData: FormData) {
  'use server'
  
  const query = formData.get('q') as string
  
  // Redirect to search results
  redirect(serialize('/search', { q: query }))
}

API Routes

import { createSerializer, parseAsInteger } from 'nuqs/server'

const serialize = createSerializer({
  page: parseAsInteger,
  limit: parseAsInteger
})

export async function GET(request: Request) {
  const nextPageUrl = serialize(
    new URL(request.url),
    { page: 2, limit: 20 }
  )
  
  return Response.json({
    data: [...],
    nextPage: nextPageUrl
  })
}

Complex Serialization

import {
  createSerializer,
  parseAsString,
  parseAsInteger,
  parseAsArrayOf,
  parseAsIsoDateTime,
  parseAsStringLiteral
} from 'nuqs/server'

const serialize = createSerializer(
  {
    search: parseAsString,
    page: parseAsInteger.withDefault(1),
    limit: parseAsInteger.withDefault(20),
    tags: parseAsArrayOf(parseAsString),
    from: parseAsIsoDateTime,
    to: parseAsIsoDateTime,
    sortBy: parseAsStringLiteral(['asc', 'desc'] as const)
  },
  {
    urlKeys: {
      search: 'q',
      limit: 'per_page'
    }
  }
)

serialize({
  search: 'laptop',
  page: 2,
  tags: ['electronics', 'sale'],
  from: new Date('2024-01-01'),
  sortBy: 'desc'
})
// "?q=laptop&page=2&tags=electronics,sale&from=2024-01-01T00:00:00.000Z&sortBy=desc"

Type Safety

The serializer is fully type-safe:
const serialize = createSerializer({
  count: parseAsInteger,
  active: parseAsBoolean,
  tags: parseAsArrayOf(parseAsString)
})

// ✅ Valid
serialize({
  count: 42,
  active: true,
  tags: ['a', 'b']
})

// ❌ TypeScript error: wrong type
serialize({ count: 'not a number' })

// ❌ TypeScript error: unknown key
serialize({ unknown: 'value' })

// ✅ Valid: null removes parameter
serialize({ count: null })

Serializer Guide

Learn how to use serializers for URLs

createLoader

Parse search params with createLoader

createSearchParamsCache

Cache search params in server components

Parsers

Learn about available parser types

Build docs developers (and LLMs) love