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