Type-safe URL Search Params

parseURLSearchParam is a small utility function that validates and transforms URL search parameters into a structured object based on a given zod schema.

Helpful for handling URL parameters in TypeScript, ensuring data conforms to a specified structure and type through validation. This is especially useful in web applications where data from URLs needs to be clean, type-safe, and conform to expected formats.

import { z } from "zod";

export function parseURLSearchParams<T extends z.ZodTypeAny>(
  schema: T,
  params: URLSearchParams,
) {
  const data: Record<string, string[] | string | undefined> = {};

  for (const key of params.keys()) {
    const value = params.getAll(key).filter(v => v !== String(undefined));
    if (value.length > 1) data[key] = value;
    else data[key] = value.at(0);
  }

  return schema.parse(data) as z.infer<T>;
}

Example usage

import { z } from "zod";

/** Pagination - current page number */
const zCurrentPage = z.coerce.number().positive().min(1).default(1).catch(1);

/** Sort direction */
const zDirection = z.enum(["ASC", "DESC"]).default("DESC").catch("DESC");

/** Sort by field */
const zSortBy = z.enum(["NAME", "PRICE", "CREATE_DATE"]).catch("PRICE");

const zStringArray = z
  .string()
  .array()
  .or(z.string())
  .optional()
  .transform(v => (typeof v === "string" ? [v] : v));

const ProductFilterSchema = z.object({
  search: z.string().optional(),
  page: zCurrentPage,
  sortBy: zSortBy,
  direction: zDirection,
  productVariants: zStringArray,
});
import { useSearchParams } from "react-router-dom";

function Component() {
  const [searchParams] = useSearchParams();
  const filters = parseURLSearchParams(ProductFilterSchema, searchParams);

  return (
    <div>
      <ProductList filters={filters} />
    </div>
  );
}