paramour
Reference

@paramour-js/nuqs

The nuqs adapter API.

@paramour-js/nuqs is a thin adapter (design-10) that derives nuqs parsers from the codecs a route already declares, so URL state has a single source of truth: presence, defaults, catch recovery, and equality are all read off the codec, never declared a second time. The output is ordinary nuqs currency — useQueryState(s), withOptions, createSerializer, createLoader, and the server cache all compose untouched. Internally the adapter imports from nuqs/server, so deriving parsers is safe in server code too.

For the guided walkthrough, see the nuqs guide. This page is the API reference.

nuqsParser

Derives one nuqs parser from one codec.

function nuqsParser<C extends AnyCodec>(
  codec: C & CompatibleCodec<C>,
): NuqsParserOf<C>;

Pass the codec exactly as it sits in a route's search config — .optional(), .default(), .catch() already applied. The name mirrors nuqs's parseAs* vocabulary from the call site's perspective: it is the parser for that codec's output.

"use client";

import {  } from "@paramour-js/nuqs";
import {  } from "nuqs";
import {  } from "paramour";

export function () {
  const [q, ] = ("q", (.().()));
const q: string | null
return < ={() => void (..)} ={ ?? ""} />; }

The derivation rules, per codec shape:

Codec shapeDerived nuqs parser
required or .optional()single parser, nullable read (null = absent)
.default(value)withDefault(value) — non-nullable read
.default(() => value) (factory)nullable read — no withDefault
.catch(fallback)malformed values recover to the fallback
p.array(…) (arity-many)multi (repeated-key) parser with withDefault([])
p.csv(…)single parser reading E[] | null (one key on the wire)

Gotchas worth knowing:

  • Factory defaults stay nullable on purpose. A factory default is time-varying by declaration; freezing one value into withDefault would lie. Absent reads null — apply the factory at the read site if you want the paramour-decoded shape.
  • .catch() parity comes first. A malformed value recovers exactly as the server decode would; only a codec without catch falls back to nuqs's null. Anything that isn't a paramour ParseError — including a throwing catch factory — propagates loudly.
  • Equality is wire-form equality. The parser's eq compares serialized wire strings — the same judgment paramour's own default elision makes — so nuqs's clearOnDefault and paramour's URL elision agree by construction, for every codec kind including p.custom.
  • Value-form defaults are snapshotted once at derivation time. Mutating a reference-typed default (an array, say) after deriving is unsupported.
  • Plain-JavaScript callers passing something that is not a codec get a loud ParamourError at derivation time, mirroring the compile-time judgments below.

nuqsParsers

Derives a whole nuqs parser map from a route object (the common case) or a bare SearchConfig.

function nuqsParsers<R extends AnyRoute>(
  route: CompatibleRoute<R> & R,
): NuqsParserMap<R["~search"]>;
function nuqsParsers<S extends SearchConfig>(
  config: CompatibleConfig<S> & S,
): NuqsParserMap<S>;

Each key gets the same per-codec derivation as nuqsParser; the result is a plain object you hand to useQueryStates, createSerializer, createLoader, or the server cache as-is:

app/products/toolbar.tsx
// @filename: app/products/route.def.ts
import { ,  } from "paramour";

export const  = ("/products", {
  : {
    : .().(1),
    : .().(),
    : .(),
  },
});

// @filename: app/products/toolbar.tsx
"use client";

import {  } from "@paramour-js/nuqs";
import {  } from "nuqs";

import {  } from "./route.def";

export function () {
  const [, ] = (());

  .page;
page: number
.q;
q: string | null
.tags;
tags: string[]
return ( < ={() => void ({ : . + 1 })}> next page </> ); }

page reads non-nullable (its .default(1) became withDefault), q reads string | null (nuqs's spelling of absent), and tags is a repeated-key multi-parser that reads [] when absent — matching core's decode, where an absent arity-many key and [] are the same wire state.

Gotchas:

  • Routes without per-key codecs are rejected — see NoNuqsTwin below. rawSearch routes validate the whole search object with one schema, so there is nothing per-key to derive from; search-less routes and empty configs have no keys at all. The same judgments are loud ParamourErrors at runtime for plain-JS callers.
  • Route vs. config detection is by value shape: only a route carries a router-kind string on its ~router brand, so a search config with a key literally named "~router" (legal — wire keys are arbitrary strings) still takes the config path.
  • The map is built key-safely (Object.entriesObject.fromEntries), so keys like "__proto__" become ordinary own properties of the result.

NoNuqsTwin

The compile-time rejection brand for codec shapes with no faithful nuqs translation.

interface NoNuqsTwin<Reason extends string> {
  readonly [noTwinReason]: Reason;
}

Some shapes cannot be translated without changing their meaning, and the adapter refuses at the call site rather than silently conflating semantics. The mechanism: incompatible arguments are intersected with NoNuqsTwin, whose single symbol-keyed property is unforgeable (the symbol is not exported), so no runtime value inhabits the type and the call fails to compile — with the Reason literal surfacing in the error text.

Rejected shapes:

  • Codec output includes null. nuqs reserves null to mean "absent or unparseable", so a codec whose legitimate output includes null would have its null indistinguishable from a parse failure on the nuqs side.
  • rawSearch routes — the whole search object is validated by one schema; there are no per-key codecs to derive from.
  • Search-less routes and empty search configs — no keys to derive parsers for.
import {  } from "@paramour-js/nuqs";
import {  } from "paramour";

const  = .<null | string>({
  : () => ( === "null" ? null : ),
  : () =>  ?? "null",
});

(maybe);
Argument of type 'Codec<string | null, "required", false, "single", boolean>' is not assignable to parameter of type 'Codec<string | null, "required", false, "single", boolean> & NoNuqsTwin<"codec output includes null, which nuqs reserves for absent/unparseable">'. Property '[noTwinReason]' is missing in type 'Codec<string | null, "required", false, "single", boolean>' but required in type 'NoNuqsTwin<"codec output includes null, which nuqs reserves for absent/unparseable">'.

The null-output rejection is type-level only: a codec's output type is a phantom (~out), so there is no runtime probe, and a null that slips past the types degrades to nuqs's native null semantics. The structural rejections (non-codec values, rawSearch, empty configs) do have runtime backstops — plain-JavaScript callers get the same judgments as ParamourErrors.

  • NuqsParserMap<S extends SearchConfig> — the parser map nuqsParsers derives from a whole search config: { [K in keyof S]: NuqsParserOf<S[K]> }, with the readonly modifier a route's const-inferred search slot carries stripped, so route-derived and bare-config-derived maps have the identical shape.
  • NuqsParserOf<C extends AnyCodec> — the parser type nuqsParser derives from one codec: a multi (repeated-key) parser with defaultValue: [] for arity-many codecs, a withDefault (non-nullable) single parser for value-form defaults, and a nullable single parser otherwise. A hand-typed Codec<…, "defaulted"> whose default-elides flag was left at its boolean default falls to the nullable branch — the safe reading.

On this page