valibot-search-params
v0.6.0
Published
`valibot-search-params` is a lightweight, type-safe bidirectional serialization and validation utility that maps plain JavaScript objects to `URLSearchParams` and parses `URLSearchParams` back into structured objects validated against **Valibot** schemas.
Readme
valibot-search-params
valibot-search-params is a lightweight, type-safe bidirectional serialization and validation utility that maps plain JavaScript objects to URLSearchParams and parses URLSearchParams back into structured objects validated against Valibot schemas.
Requirements and Guardrails
- Peer Dependency: Requires
valibotversion1.0.0or greater. - Runtime Compatibility: Fully compatible with modern browser environments, server-side runtimes (Node.js, Deno, Bun), and Edge workers.
Installation
Install the package via npm:
npm install valibot-search-paramsQuick Start
1. Serialize an Object to URLSearchParams
Convert a flat or nested JavaScript object into an instance of URLSearchParams using objectToSearchParams:
import { objectToSearchParams } from "valibot-search-params";
const filters = {
search: "typescript",
page: 2,
tags: ["oss", "web"],
meta: { active: true }
};
const searchParams = objectToSearchParams(filters);
console.log(searchParams.toString());
// Output: "search=typescript&page=2&tags=oss&tags=web&meta=%7B%22active%22%3Atrue%7D"2. Parse URLSearchParams to a Validated Object
Parse a URLSearchParams instance back into a validated JavaScript object using searchParamsToObject guided by a Valibot schema:
import * as v from "valibot";
import { searchParamsToObject } from "valibot-search-params";
const Schema = v.object({
search: v.string(),
page: v.number(),
tags: v.array(v.string()),
meta: v.object({ active: v.boolean() })
});
const params = new URLSearchParams("search=typescript&page=2&tags=oss&tags=web&meta=%7B%22active%22%3Atrue%7D");
const parsed = searchParamsToObject(Schema, params);
const validation = v.safeParse(Schema, parsed);
if (validation.success) {
console.log(validation.output);
// Output: { search: 'typescript', page: 2, tags: ['oss', 'web'], meta: { active: true } }
}Key Features
- Type Coercion: Automatically casts raw query parameter strings into correct runtime types (e.g. converting "123" to
123for anumberschema, or "true" totruefor abooleanschema). - Nested JSON Serialization: Serializes nested objects and arrays of complex structures into clean, URI-safe JSON strings rather than using verbose dot-notation or brackets.
- Variant Schema Resolution: Safely resolves discriminated unions (Valibot
variantschemas) based on an explicit discriminator query field. - BigInt Support: Robust, out-of-the-box BigInt serialization and parsing, converting
bigintvalues into clean numeric strings for URL parameters and handling raw numeric representations inside JSON payloads.
API Reference
objectToSearchParams
Converts a plain JavaScript object into a URLSearchParams instance.
Signature
export function objectToSearchParams(
obj: Record<string, unknown>,
prefix?: string
): URLSearchParams;Parameters
obj: The plain object containing properties to serialize.prefix: An optional namespace prefix. If provided, prepends${prefix}.to all serialized parameter keys.
Returns
Returns a URLSearchParams instance populated with the serialized object properties.
searchParamsToObject
Parses values from a URLSearchParams instance into a plain object matching the structure of a given Valibot schema.
Signature
export function searchParamsToObject(
schema: ObjectSchema<any, any> | VariantSchema<any, VariantOptions<any>, any>,
searchParams: URLSearchParams,
prefix?: string
): Record<string, unknown>;Parameters
schema: The Valibotobjectorvariant(discriminated union) schema to use as a blueprint.searchParams: TheURLSearchParamsinstance containing the query parameter strings.prefix: An optional namespace prefix. If provided, retrieves values only from parameter keys prefixed with${prefix}..
Returns
Returns a plain Record<string, unknown> object containing the parsed and type-casted properties ready for validation.
Advanced Examples
Handling Arrays
Simple primitive arrays are serialized as multiple identical key entries in URLSearchParams. Complex structures (like arrays of objects) are JSON-stringified.
import * as v from "valibot";
import { objectToSearchParams, searchParamsToObject } from "valibot-search-params";
const Schema = v.object({
tags: v.array(v.string()), // Primitive array
items: v.array(v.object({ id: v.number() })) // Array of objects
});
const data = {
tags: ["oss", "web"],
items: [{ id: 1 }, { id: 2 }]
};
const params = objectToSearchParams(data);
console.log(params.toString());
// Output: "tags=oss&tags=web&items=%7B%22id%22%3A1%7D&items=%7B%22id%22%3A2%7D"
const parsed = searchParamsToObject(Schema, params);
console.log(parsed);
// Output: { tags: ['oss', 'web'], items: [ { id: 1 }, { id: 2 } ] }Handling Nested Objects
Nested objects are automatically serialized as JSON strings in the query parameters and parsed back safely during extraction.
import * as v from "valibot";
import { objectToSearchParams, searchParamsToObject } from "valibot-search-params";
const Schema = v.object({
meta: v.object({
active: v.boolean(),
config: v.object({
theme: v.string()
})
})
});
const data = {
meta: {
active: true,
config: { theme: "dark" }
}
};
const params = objectToSearchParams(data);
console.log(params.toString());
// Output: "meta=%7B%22active%22%3Atrue%2C%22config%22%3A%7B%22theme%22%3A%22dark%22%7D%7D"
const parsed = searchParamsToObject(Schema, params);
console.log(parsed);
// Output: { meta: { active: true, config: { theme: 'dark' } } }Handling Variants (Discriminated Unions)
searchParamsToObject evaluates the discriminator field from the query string to select and parse the matching sub-schema.
import * as v from "valibot";
import { objectToSearchParams, searchParamsToObject } from "valibot-search-params";
const ShapeSchema = v.variant("type", [
v.object({
type: v.literal("circle"),
radius: v.number()
}),
v.object({
type: v.literal("rect"),
width: v.number(),
height: v.number()
})
]);
const rectData = {
type: "rect",
width: 10,
height: 20
};
const params = objectToSearchParams(rectData);
// Output: "type=rect&width=10&height=20"
const parsed = searchParamsToObject(ShapeSchema, params);
console.log(parsed);
// Output: { type: 'rect', width: 10, height: 20 }Behavioral Guardrails & Edge Cases
1. JSON Serialization of Nested Structures
Instead of generating verbose bracketed dot-notation (e.g. meta[config][theme]=dark), objectToSearchParams serializes all sub-objects and arrays of objects using standard JSON stringification. During parsing, searchParamsToObject utilizes schema metadata to selectively identify these complex fields and deserialize them before passing the structure to Valibot.
2. Empty String Literals
[!NOTE]
searchParamsToObjectskips literal keys that are empty strings. If your schema defines a literal parameter as an empty string (e.g.v.literal("")),searchParamsToObjecttreats the query key as missing (undefined), resulting in a Valibot validation failure. Make literal keys non-empty or optional if they may be omitted.
3. BigInt Serialization Format
When converting raw objects to query parameters, bigint values are formatted as standard numeric strings (e.g. "42"). These numeric strings are then reconstructed back into JavaScript BigInt instances during parsing, guided by the bigint schema entries (both for top-level query fields and nested JSON structures).
