useSearchParams
Reactive, schema-validated URL search params for Svelte/SvelteKit
useSearchParams
provides a reactive, type-safe, and schema-driven way to manage URL search
parameters in Svelte/SvelteKit apps. It supports validation, default values, compression,
debouncing, and history control.
Demo
Note
Requirements
@sveltejs/kit
must be installed in your project.- Uses Standard Schema for schema validation and type inference.
Usage
Define schema:
// schemas.ts
import { z } from "zod";
// Version 3.24.0+
export const productSearchSchema = z.object({
page: z.coerce.number().default(1),
filter: z.string().default(""),
sort: z.enum(["newest", "oldest", "price"]).default("newest")
});
In your svelte code:
<script lang="ts">
import { useSearchParams } from "runed/kit";
import { productSearchSchema } from './schemas';
const params = useSearchParams(productSearchSchema);
// Access parameters directly
const page = $derived(params.page); // number (defaults to 1)
const sort = $derived(params.sort); // 'newest' | 'oldest' | 'price'
// Update parameters directly
params.page = 2; // Updates URL to include ?page=2
// Updates URL to include ?page=3&sort=oldest
params.update({ page: 3, sort: 'oldest' });
// Resets all parameters to their default values
params.reset();
// Returns URLSearchParams object with all current parameter values
params.toURLSearchParams();
/**
* You can watch for changes to the URLSearchParams object
* For example:
* watch(() => params.toURLSearchParams(), () => {
* // Do something whenever the URLSearchParams object changes
* })
*/
</script>
<-- Great for binding to input fields -->
<input type="text" bind:value={params.filter} />
In your load function:
import { validateSearchParams } from "runed/kit";
import { productSearchSchema } from "./schemas";
export const load = ({ url, fetch }) => {
// Get validated search params as URLSearchParams object
// If you use a custom compressedParamName in useSearchParams, provide it here too:
const { searchParams } = validateSearchParams(url, productSearchSchema);
// Use URLSearchParams directly with fetch
const response = await fetch(`/api/products?${searchParams.toString()}`);
return {
products: await response.json()
};
};
Features
- Schema Validation: Define types, defaults, and structure for each param.
- Default Values: Missing params are auto-filled with defaults.
- Type Safety: All values are parsed and validated to the schema.
- Compression: Store all params in a single compressed
_data
param for cleaner URLs. - Debounce: Optionally debounce updates for smoother UX.
- History Control: Choose between push/replace state.
- In-memory Mode: Params are kept in memory, not in the URL.
- Invalid Param Handling: Invalid values are replaced with defaults and removed from the URL.
useSearchParams
Hook to create a reactive search params object with property access
This client-side hook automatically updates the URL when parameters change. It provides type-safe access to URL search parameters through direct property access.
Parameters:
schema
: A validation schema compatible with StandardSchemaV1options
: Configuration options that affect URL behavior
Returns:
- A reactive object for working with typed search parameters
Available options:
showDefaults
(boolean): When true, parameters with default values will be shown in the URL. When false (default), parameters with default values will be omitted from the URL.debounce
(number): Milliseconds to delay URL updates when parameters change. Useful to avoid cluttering browser history when values change rapidly (default: 0, no debounce).pushHistory
(boolean): Controls whether URL updates create new browser history entries. If true (default), each update adds a new entry to the browser history. If false, updates replace the current URL without creating new history entries.compress
(boolean): When true, all parameters are compressed into a single parameter using lz-string compression. This helps reduce URL length and provides basic obfuscation (default: false). Use validateSearchParams with the same compressedParamName option when handling compressed URLs server-side.compressedParamName
(string): The name of the parameter used to store compressed data when compression is enabled. Customize this to avoid conflicts with parameters in your schema. Default is_data
.updateURL
(boolean): When true (default), the URL is updated when parameters change. When false, only in-memory parameters are updated.
Example with Zod:
import { z } from "zod";
const productSearchSchema = z.object({
page: z.number().default(1),
filter: z.string().default(""),
sort: z.enum(["newest", "oldest", "price"]).default("newest")
});
const params = useSearchParams(productSearchSchema);
// Access parameters directly
const page = $derived(params.page); // number (defaults to 1)
const sort = $derived(params.sort); // 'newest' | 'oldest' | 'price'
Example with options:
// Show default values in URL, debounce updates by 300ms,
// don't create new history entries, and compress params
const params = useSearchParams(schema, {
showDefaults: true,
debounce: 300,
pushHistory: false,
compress: true,
compressedParamName: '_compressed' // Custom name to avoid conflicts
});
// Great for binding to input fields (updates URL without cluttering history)
<input type="text" bind:value={params.search} />
// Resulting URL will be something like: /?_compressed=N4IgDgTg9g...
Example with Valibot:
import * as v from "valibot";
const productSearchSchema = v.object({
page: v.optional(v.fallback(v.number(), 1), 1),
filter: v.optional(v.fallback(v.string(), ""), ""),
sort: v.optional(v.fallback(v.picklist(["newest", "oldest", "price"]), "newest"), "newest")
});
const params = useSearchParams(productSearchSchema);
Example with Arktype:
import { type } from "arktype";
const productSearchSchema = type({
page: "number = 1",
filter: 'string = ""',
sort: '"newest" | "oldest" | "price" = "newest"'
});
const params = useSearchParams(productSearchSchema);
Or with our built-in schema creator (no additional dependencies)
createSearchParamsSchema
Creates a simple schema compatible with Standard Schema without requiring external validation libraries.
This is a lightweight alternative to using full schema validation libraries like Zod, Valibot, or Arktype. Use this when you need basic type conversion and default values without adding dependencies.
Limitations:
- For 'array' type: supports basic arrays, but doesn't validate array items
- For 'object' type: supports generic objects, but doesn't validate nested properties
- No custom validation rules or transformations
- No granular reactivity: Changes to nested properties require reassigning the entire value
- ❌
params.config.theme = 'dark'
(won't trigger URL update) - ✅
params.config = {...params.config, theme: 'dark'}
(will trigger URL update)
- ❌
For complex validation needs (nested validation, refined rules, etc.), use a dedicated validation library instead.
const productSearchSchema = createSearchParamsSchema({
// Basic types with defaults
page: { type: "number", default: 1 },
filter: { type: "string", default: "" },
sort: { type: "string", default: "newest" },
// Array type with specific element type
tags: {
type: "array",
default: ["new"],
arrayType: "" // Specify string[] type
},
// Object type with specific shape
config: {
type: "object",
default: { theme: "light" },
objectType: { theme: "" } // Specify { theme: string } type
}
});
URL storage format:
- Arrays are stored as JSON strings:
?tags=["sale","featured"]
- Objects are stored as JSON strings:
?config={"theme":"dark","fontSize":14}
- Primitive values are stored directly:
?page=2&filter=red
validateSearchParams
A utility function to extract, validate and convert URL search parameters to URLSearchParams
This function makes it easy to use the same schema validation in both client-side components (via useSearchParams
) and server-side load functions. Unlike `useSearchParams, this function doesn't
modify the URL - it only validates parameters and returns them as a new URLSearchParams object.
Handles both standard URL parameters and compressed parameters (when compression is enabled).
Parameters:
url
: The URL object from SvelteKit load functionschema
: A validation schema (createSearchParamsSchema, Zod, Valibot, etc.)options
: Optional configuration (like customcompressedParamName
)
Returns:
- An object with
searchParams
anddata
properties,searchParams
being the validatedURLSearchParams
anddata
being the validated object
Example with SvelteKit page or layout load function:
import { validateSearchParams } from "runed/kit";
import { productSchema } from "./schemas";
export const load = ({ url, fetch }) => {
// Get validated search params as URLSearchParams object
// If you use a custom compressedParamName in useSearchParams, provide it here too:
const { searchParams } = validateSearchParams(url, productSchema, {
compressedParamName: "_compressed"
});
// Use URLSearchParams directly with fetch
const response = await fetch(`/api/products?${searchParams.toString()}`);
return {
products: await response.json()
};
};
Reactivity Limitations
Understanding Reactivity Scope
useSearchParams
provides top-level reactivity only. This means:
✅ Works (Direct property assignment):
<script>
const params = useSearchParams(schema);
// These trigger URL updates
params.page = 2;
params.filter = "active";
params.config = { theme: "dark", size: "large" };
params.items = [...params.items, newItem];
</script>
❌ Doesn't work (Nested property mutations):
<script>
const params = useSearchParams(schema);
// These DON'T trigger URL updates
params.config.theme = "dark"; // Nested object property
params.items.push(newItem); // Array method
params.items[0].name = "updated"; // Array item property
delete params.config.oldProp; // Property deletion
</script>
Why This Design Choice
useSearchParams
was designed to prioritize simplicity, type safety, and ease of use over deep
reactivity. This design choice offers several benefits:
✅ What You Get
- Simple, predictable API:
params.page = 2
always works - Full TypeScript support: Perfect autocomplete and type checking
- Clean URLs: Objects serialize to readable JSON strings
- Performance: No overhead from tracking deep object changes
- Reliability: No edge cases with complex nested proxy behaviors
Type Definitions
interface SearchParamsOptions {
/**
* If true, parameters set to their default values will be shown in the URL.
* If false, parameters with default values will be omitted from the URL.
* @default false
*/
showDefaults?: boolean;
/**
* The number of milliseconds to delay URL updates when parameters change.
* This helps avoid cluttering browser history when values change rapidly
* (like during typing in an input field).
* @default 0 (no debounce)
*/
debounce?: number;
/**
* Controls whether URL updates create new browser history entries.
* If true (default), each update adds a new entry to the browser history.
* If false, updates replace the current URL without creating new history entries.
* @default true
*/
pushHistory?: boolean;
/**
* Enable lz-string compression for all parameters.
* When true, all parameters are compressed into a single parameter in the URL.
* This helps reduce URL length and provides basic parameter obfuscation.
* @default false
*/
compress?: boolean;
/**
* The name of the parameter used to store compressed data when compression is enabled.
* You can customize this to avoid conflicts with your schema parameters.
* For example, if your schema already uses '_data', you might want to use '_compressed' or another unique name.
* @default '_data'
*/
compressedParamName?: string;
/**
* Controls whether to update the URL when parameters change.
* If true (default), changes to parameters will update the URL.
* If false, parameters will only be stored in memory without updating the URL.
* Note: When false, compress option will be ignored.
* @default true
*/
updateURL?: boolean;
}
type ReturnUseSearchParams<Schema extends StandardSchemaV1> = {
[key: string]: any; // Typed, reactive params
/**
* Update multiple parameters at once
*
* This is more efficient than setting multiple parameters individually
* because it only triggers one URL update or one in-memory store update.
*
* @param values An object containing parameter key-value pairs to update.
* For example: params.update({ page: 1, sort: 'newest' })
*/
update(values: Partial<StandardSchemaV1.InferOutput<Schema>>): void;
/**
* Reset all parameters to their default values
*
* This method removes all current URL parameters or in-memory parameters
* and optionally sets parameters with non-default values back to their defaults.
*
* @param showDefaults Whether to show default values in the URL or in-memory store after reset.
* If not provided, uses the instance's showDefaults option.
*/
reset(showDefaults?: boolean): void;
/**
* Convert the current schema parameters to a URLSearchParams object
* This includes all values defined in the schema, regardless of their presence in the URL
* @returns URLSearchParams object containing all current parameter values
*/
toURLSearchParams(): URLSearchParams;
};
/**
* Schema type for createSearchParamsSchema
* Allows specifying more precise types for arrays and objects
*/
type SchemaTypeConfig<ArrayType = unknown, ObjectType = unknown> =
| { type: "string"; default?: string }
| { type: "number"; default?: number }
| { type: "boolean"; default?: boolean }
| { type: "array"; default?: ArrayType[]; arrayType?: ArrayType }
| { type: "object"; default?: ObjectType; objectType?: ObjectType };