Documentation Index Fetch the complete documentation index at: https://mintlify.com/47ng/nuqs/llms.txt
Use this file to discover all available pages before exploring further.
createParser
Wrap a set of parse/serialize functions into a builder pattern parser for use with nuqs hooks.
export function createParser < T >(
parser : Require < SingleParser < T >, 'parse' | 'serialize' >
) : SingleParserBuilder < T >
From: packages/nuqs/src/parsers.ts:150-193
Parameters
Parser configuration object. parse
(value: string) => T | null
required
Function to convert a URL query string value into a typed state value. Must return null for invalid inputs to ensure graceful degradation.
serialize
(value: T) => string
required
Function to convert a typed state value back into a URL query string. Must be lossless - serializing and then parsing should return the original value.
Custom equality function for comparing state values. Defaults to referential equality (a === b). Provide this when parsing objects or arrays.
Returns
A parser builder with the following methods and properties: parse
(value: string) => T | null
The parse function from the input.
The serialize function from the input.
Equality function (defaults to (a, b) => a === b).
withDefault
(defaultValue: NonNullable<T>) => SingleParserBuilder<T>
Returns a new parser with a default value, making the hook state non-nullable. Setting state to the default value clears the query parameter from the URL (unless clearOnDefault: false).
withOptions
(options: Options) => this
Returns the parser with navigation options (history, scroll, shallow, etc.) pre-configured.
parseServerSide
(value: string | string[] | undefined) => T | null
Server-side parsing helper (deprecated in favor of loaders).
Usage
Basic Custom Parser
import { createParser } from 'nuqs'
const parseAsHexColor = createParser ({
parse ( query : string ) {
// Validate hex color format
if ( ! / ^ [ 0-9A-Fa-f ] {6} $ / . test ( query )) {
return null // Invalid format
}
return `# ${ query } `
},
serialize ( value : string ) {
// Remove # prefix for URL
return value . replace ( '#' , '' )
}
})
const [ color , setColor ] = useQueryState (
'color' ,
parseAsHexColor . withDefault ( '#ff0000' )
)
// URL: ?color=ff0000
// State: '#ff0000'
setColor ( '#00ff00' )
// URL: ?color=00ff00
Parser with Custom Equality
import { createParser } from 'nuqs'
type Point = { x : number ; y : number }
const parseAsPoint = createParser ({
parse ( query : string ) {
const match = query . match ( / ^ ( \d + ) , ( \d + ) $ / )
if ( ! match ) return null
return {
x: parseInt ( match [ 1 ]),
y: parseInt ( match [ 2 ])
}
},
serialize ( value : Point ) {
return ` ${ value . x } , ${ value . y } `
},
eq ( a : Point , b : Point ) {
// Deep equality for objects
return a . x === b . x && a . y === b . y
}
})
const [ cursor , setCursor ] = useQueryState (
'cursor' ,
parseAsPoint . withDefault ({ x: 0 , y: 0 })
)
// URL: ?cursor=100,200
// State: { x: 100, y: 200 }
Composing Existing Parsers
import { createParser , parseAsHex } from 'nuqs'
type RGB = { r : number ; g : number ; b : number }
const parseAsRgb = createParser ({
parse ( query : string ) {
if ( query . length !== 6 ) {
return null
}
return {
r: parseAsHex . parse ( query . slice ( 0 , 2 )) ?? 0x00 ,
g: parseAsHex . parse ( query . slice ( 2 , 4 )) ?? 0x00 ,
b: parseAsHex . parse ( query . slice ( 4 )) ?? 0x00
}
},
serialize ({ r , g , b } : RGB ) {
return (
parseAsHex . serialize ( r ) +
parseAsHex . serialize ( g ) +
parseAsHex . serialize ( b )
)
},
eq ( a , b ) {
return a . r === b . r && a . g === b . g && a . b === b . b
}
})
const [ color , setColor ] = useQueryState (
'color' ,
parseAsRgb . withDefault ({ r: 255 , g: 0 , b: 0 })
)
// URL: ?color=ff0000
// State: { r: 255, g: 0, b: 0 }
Parser with Validation
import { createParser } from 'nuqs'
const parseAsEmail = createParser ({
parse ( query : string ) {
// Basic email validation
const emailRegex = / ^ [ ^ \s@ ] + @ [ ^ \s@ ] + \. [ ^ \s@ ] + $ /
if ( ! emailRegex . test ( query )) {
return null
}
return query
},
serialize: String
})
const [ email , setEmail ] = useQueryState ( 'email' , parseAsEmail )
// URL: ?email=user@example.com
// State: 'user@example.com'
// URL: ?email=invalid-email
// State: null (validation failed)
Builder Methods
withDefault
Set a default value to make the hook state non-nullable.
const parser = createParser ({
parse : v => parseInt ( v ) || null ,
serialize: String
})
const parserWithDefault = parser . withDefault ( 0 )
// Without default
const [ count1 , setCount1 ] = useQueryState ( 'count' , parser )
// Type: number | null
// With default
const [ count2 , setCount2 ] = useQueryState ( 'count' , parserWithDefault )
// Type: number (never null)
Behavior:
When URL has no value: returns default instead of null
When setting to default: clears query parameter from URL (unless clearOnDefault: false)
When setting to null: clears query parameter and returns default value
From: packages/nuqs/src/parsers.ts:79-103
withOptions
Pre-configure navigation options at the parser level.
const parser = createParser ({
parse : v => v ,
serialize: String
})
const parserWithOptions = parser
. withOptions ({
history: 'push' , // Create new history entry
scroll: true , // Scroll to top on update
shallow: false // Trigger server request
})
const [ search , setSearch ] = useQueryState ( 'q' , parserWithOptions )
// Every update uses these options
// Options can still be overridden per update
setSearch ( 'query' , { history: 'replace' })
Available Options:
history: 'push' | 'replace' - How updates affect browser history
scroll: boolean - Scroll to top after update
shallow: boolean - Client-only updates (default: true)
throttleMs: number - Throttle URL updates (deprecated, use limitUrlUpdates)
limitUrlUpdates: Rate limit configuration
startTransition: React transition function
clearOnDefault: boolean - Clear URL when setting to default (default: true)
From: packages/nuqs/src/parsers.ts:61-62 and packages/nuqs/src/parsers.ts:186-192
Chaining Methods
Builder methods can be chained in any order:
const parser = parseAsInteger
. withDefault ( 1 )
. withOptions ({ history: 'push' })
. withOptions ({ scroll: true })
. withDefault ( 10 ) // Overrides previous default
const [ page , setPage ] = useQueryState ( 'page' , parser )
// Type: number (default: 10)
// Options: { history: 'push', scroll: true }
Implementation Details
Full Implementation
export function createParser < T >(
parser : Require < SingleParser < T >, 'parse' | 'serialize' >
) : SingleParserBuilder < T > {
function parseServerSideNullable ( value : string | string [] | undefined ) {
if ( typeof value === 'undefined' ) {
return null
}
let str = ''
if ( Array . isArray ( value )) {
// Follow the spec:
// https://url.spec.whatwg.org/#dom-urlsearchparams-get
if ( value [ 0 ] === undefined ) {
return null
}
str = value [ 0 ]
}
if ( typeof value === 'string' ) {
str = value
}
return safeParse ( parser . parse , str )
}
return {
type: 'single' ,
eq : ( a , b ) => a === b ,
... parser ,
parseServerSide: parseServerSideNullable ,
withDefault ( defaultValue ) {
return {
... this ,
defaultValue ,
parseServerSide ( value ) {
return parseServerSideNullable ( value ) ?? defaultValue
}
}
},
withOptions ( options : Options ) {
return {
... this ,
... options
}
}
}
}
From: packages/nuqs/src/parsers.ts:150-193
safeParse Helper
The safeParse helper wraps the parse function to handle errors gracefully:
// From packages/nuqs/src/lib/safe-parse.ts
function safeParse < T >(
parse : ( value : string ) => T | null ,
value : string ,
path ?: string
) : T | null {
try {
return parse ( value )
} catch ( error ) {
console . error ( `[nuqs] Parse error ${ path ? ` at ${ path } ` : '' } :` , error )
return null
}
}
Best Practices
// ✅ Good
const parseAsPositiveInt = createParser ({
parse ( v ) {
const num = parseInt ( v )
if ( num > 0 ) return num
return null // Invalid: negative or NaN
},
serialize: String
})
// ❌ Bad - throws errors
const parseAsPositiveInt = createParser ({
parse ( v ) {
const num = parseInt ( v )
if ( num <= 0 ) throw new Error ( 'Must be positive' )
return num
},
serialize: String
})
2. Ensure Lossless Serialization
// ✅ Good - preserves precision
const parseAsFloat = createParser ({
parse ( v ) {
const num = parseFloat ( v )
return num == num ? num : null
},
serialize: String // No data loss
})
// ❌ Bad - loses precision
const parseAsFloat = createParser ({
parse: parseFloat ,
serialize : v => v . toFixed ( 2 ) // Rounds to 2 decimals!
})
3. Provide Custom Equality for Objects
// ✅ Good - custom equality
const parseAsRange = createParser ({
parse ( v ) {
const [ min , max ] = v . split ( '-' ). map ( Number )
return { min , max }
},
serialize : ({ min , max }) => ` ${ min } - ${ max } ` ,
eq : ( a , b ) => a . min === b . min && a . max === b . max
})
// ❌ Bad - referential equality won't work
const parseAsRange = createParser ({
parse ( v ) {
const [ min , max ] = v . split ( '-' ). map ( Number )
return { min , max }
},
serialize : ({ min , max }) => ` ${ min } - ${ max } `
// Missing eq - clearOnDefault won't work correctly
})
4. Use Type Guards for Complex Types
type Status = 'pending' | 'active' | 'completed'
const isStatus = ( value : unknown ) : value is Status => {
return [ 'pending' , 'active' , 'completed' ]. includes ( value as string )
}
const parseAsStatus = createParser ({
parse ( query ) : Status | null {
return isStatus ( query ) ? query : null
},
serialize: String
})
createMultiParser
For creating multi-value parsers (e.g., ?tag=a&tag=b):
export function createMultiParser < T >(
parser : Omit < Require < MultiParser < T >, 'parse' | 'serialize' >, 'type' >
) : MultiParserBuilder < T >
From: packages/nuqs/src/parsers.ts:195-226
Usage:
import { createMultiParser , parseAsInteger } from 'nuqs'
const parseAsIntArray = createMultiParser ({
parse : ( values : readonly string []) => {
const parsed = values . map ( v => parseAsInteger . parse ( v )). filter ( v => v !== null )
return parsed . length > 0 ? parsed : null
},
serialize : ( values : number []) => values . map ( String )
})
const [ ids , setIds ] = useQueryState ( 'id' , parseAsIntArray . withDefault ([]))
// URL: ?id=1&id=2&id=3
// State: [1, 2, 3]
Next Steps