svelte-url-search-params Svelte Themes

Svelte Url Search Params

Simple reactive Svelte URL search params package

Svelte URL Search Params Package

A type-safe, reactive URL search params manager for SvelteKit 2 with Zod 4 validation. Simplify URL state management with automatic synchronization, type inference, and built-in validation.

Features

  • 🔒 Type-safe - Full TypeScript support with automatic type inference
  • ⚡ Reactive - Automatically syncs with browser URL using Svelte 5 runes
  • ✅ Validated - Built-in Zod schemas for all common types
  • ðŸŽŊ Simple API - Clean, intuitive interface with minimal boilerplate
  • 🔄 Auto-deduplication - Automatically removes duplicate values from arrays
  • ⚙ïļ Configurable - Debouncing, history mode, sorting, and more
  • ðŸŠķ Lightweight - Minimal dependencies, tree-shakeable

Installation

npm install svelte-url-search-params
pnpm add svelte-url-search-params
yarn add svelte-url-search-params

Requirements

  • SvelteKit 2.x
  • Svelte 5.x

Quick Start

<script lang="ts">
    import { queryParameters, p } from 'svelte-url-search-params';

    const params = queryParameters({
        search: p.string(''),
        page: p.number(1),
        enabled: p.boolean(false),
        tags: p.array<string>([]),
        ids: p.array<number>([])
    });

</script>

<input bind:value={params.search} type="text" />
<input bind:value={params.page} type="number" />
<input bind:checked={params.enabled} type="checkbox" />

API Reference

queryParameters(config, options?)

Creates a reactive query parameters manager.

Parameters:

  • config - An object mapping parameter names to Zod schemas (use p.* helpers)
  • options - Optional configuration object

Returns: Reactive proxy object with your defined parameters plus utility methods (clear(), reset())

Parameter Types (p.*)

p.string(defaultValue: string)

String parameter with trimming.

search: p.string(''); // ?search=hello

p.number(defaultValue?: number)

Number parameter. Returns null if no default and value is missing.

page: p.number(1); // ?page=42
count: p.number(); // Optional number

p.boolean(defaultValue: boolean = false)

Boolean parameter. Accepts 'true', 'false', '1', '0'.

enabled: p.boolean(false); // ?enabled=true

p.array<T>(defaultValue: T[] = [])

Array parameter. Automatically detects number vs string arrays and removes duplicates.

tags: p.array<string>([]); // ?tags=a,b,c
ids: p.array<number>([]); // ?ids=1,2,3
categories: p.array<string>(['all']); // With default

p.object<T>(defaultValue: T | null = null)

JSON object parameter. Automatically serializes/deserializes.

filter: p.object<{ min: number; max: number }>(null);
// ?filter=%7B%22min%22%3A0%2C%22max%22%3A100%7D

p.enum<T>(values: readonly T[], defaultValue: T)

Enum parameter that validates against allowed values.

status: p.enum(['draft', 'published', 'archived'], 'draft'); // ?status=published
role: p.enum(['user', 'admin'], 'user'); // ?role=admin

p.integer(defaultValue?: number)

Integer parameter (no decimals). Returns null if no default and value is missing.

age: p.integer(18); // ?age=25
count: p.integer(); // Optional integer

Options

type QueryParamsOptions = {
    /**
     * Debounce history updates by this many milliseconds
     * @default 0
     */
    debounceHistory?: number;

    /**
     * Whether to push to browser history (false = replaceState)
     * @default true
     */
    pushHistory?: boolean;

    /**
     * Whether to sort search params alphabetically
     * @default true
     */
    sort?: boolean;

    /**
     * Whether to show default values in the URL
     * @default true
     */
    showDefaults?: boolean;

    /**
     * If true, the params will not update after initial load
     * Useful for static sites or when URL should not change
     * @default false
     */
    static?: boolean;
};

Example:

const params = queryParameters(
    {
        search: p.string(''),
        page: p.number(1)
    },
    {
        debounceHistory: 300, // Wait 300ms before updating URL
        pushHistory: false, // Use replaceState instead of pushState
        sort: true, // Sort params alphabetically
        showDefaults: false // Don't show default values in URL
    }
);

Methods

Utility Methods

clear()

Clears all search parameters from the URL.

params.clear();

reset()

Resets all parameters to their default values.

params.reset();

Examples

Search with Filters

<script lang="ts">
    import { queryParameters, p } from 'svelte-url-search-params';

    const params = queryParameters({
        q: p.string(''),
        category: p.string('all'),
        minPrice: p.number(),
        maxPrice: p.number(),
        inStock: p.boolean(false)
    });
</script>

<input bind:value={params.q} placeholder="Search..." />

<select bind:value={params.category}>
    <option value="all">All Categories</option>
    <option value="electronics">Electronics</option>
    <option value="books">Books</option>
</select>

<input bind:value={params.minPrice} type="number" placeholder="Min" />
<input bind:value={params.maxPrice} type="number" placeholder="Max" />

<label>
    <input bind:checked={params.inStock} type="checkbox" />
    In Stock Only
</label>

Pagination

<script lang="ts">
    import { queryParameters, p } from 'svelte-url-search-params';

    const params = queryParameters({
        page: p.number(1),
        limit: p.number(10)
    });

    function nextPage() {
        params.page = (params.page ?? 1) + 1;
    }

    function prevPage() {
        params.page = Math.max(1, (params.page ?? 1) - 1);
    }
</script>

<button onclick={prevPage} disabled={params.page <= 1}> Previous </button>

<span>Page {params.page}</span>

<button onclick={nextPage}>Next</button>

Multi-Select with Arrays

<script lang="ts">
    import { queryParameters, p } from 'svelte-url-search-params';

    const params = queryParameters({
        tags: p.array<string>([]),
        ids: p.array<number>([])
    });

    const availableTags = ['svelte', 'typescript', 'javascript'];

    function toggleTag(tag: string) {
        if (params.tags.includes(tag)) {
            params.tags = params.tags.filter((t) => t !== tag);
        } else {
            params.tags = [...params.tags, tag];
        }
    }
</script>

{#each availableTags as tag}
    <label>
        <input type="checkbox" checked={params.tags.includes(tag)} onchange={() => toggleTag(tag)} />
        {tag}
    </label>
{/each}
<script lang="ts">
    import { queryParameters, p } from 'svelte-url-search-params';

    // Debounce URL updates to avoid excessive history entries
    const params = queryParameters(
        {
            search: p.string('')
        },
        {
            debounceHistory: 500 // Wait 500ms after last change
        }
    );
</script>

<input bind:value={params.search} type="text" placeholder="Search (debounced)..." />

Advanced Usage

Using Built-in Enum and Integer Helpers

import { queryParameters, p } from 'svelte-url-search-params';

const params = queryParameters({
    status: p.enum(['draft', 'published', 'archived'], 'draft'),
    role: p.enum(['user', 'admin'], 'user'),
    age: p.integer(18),
    rating: p.integer()
});

// Type-safe enums
params.status = 'published'; // ✅ Works
params.status = 'invalid'; // Will be reset to default 'draft'

// Integers only (no decimals)
params.age = 25; // ✅ Works
// URL: ?age=25.5 will be parsed as 25

Custom Validation

You can create custom parameter types by composing Zod schemas. Remember that URL params are always strings, so you need to transform from string to your desired type:

import { queryParameters } from 'svelte-url-search-params';
import { z } from 'zod';

// Custom email parameter
const pEmail = (defaultValue: string = '') =>
    z
        .string()
        .optional()
        .default(defaultValue)
        .transform((val) => val.trim())
        .refine((val) => !val || z.email().safeParse(val).success, {
            message: 'Invalid email'
        });

// Custom age parameter with validation
const pAge = (defaultValue: number = 18) =>
    z
        .string()
        .optional()
        .default(String(defaultValue))
        .transform((val) => {
            const num = Number(val);
            if (isNaN(num)) return defaultValue;
            return Math.min(Math.max(num, 18), 120); // Clamp between 18-120
        });

// Custom enum parameter
const pRole = () =>
    z
        .string()
        .optional()
        .default('user')
        .transform((val) => {
            return val === 'admin' ? 'admin' : 'user';
        });

const params = queryParameters({
    email: pEmail(''),
    age: pAge(18),
    role: pRole()
});

Or use the built-in helpers with additional validation:

import { queryParameters, p } from 'svelte-url-search-params';
import { z } from 'zod';

const params = queryParameters({
    // Simple custom validation on string
    username: z
        .string()
        .optional()
        .default('')
        .transform((val) => val.toLowerCase().trim()),

    // Number with custom range
    rating: z
        .string()
        .optional()
        .transform((val) => {
            const num = Number(val);
            return !isNaN(num) && num >= 1 && num <= 5 ? num : 1;
        }),

    // Or use p.* helpers as base
    tags: p.array<string>([])
});

Type Inference

TypeScript automatically infers types from your config:

const params = queryParameters({
    search: p.string(''),
    page: p.number(1),
    tags: p.array<string>([])
});

// TypeScript knows the types!
params.search; // string
params.page; // number | null
params.tags; // string[]

Backend Usage

While queryParameters() is designed for client-side use, you can use validateQueryParameters() on the backend to parse and validate URL search params in your SvelteKit server routes (+page.server.ts, +server.ts).

Page Load Function Example

// src/routes/products/+page.server.ts
import { validateQueryParameters, p } from 'svelte-url-search-params';
import { z } from 'zod';
import type { PageServerLoad } from './$types';

// Define schema for URL params
const schema = z.object({
    category: p.string('all'),
    minPrice: p.number(),
    maxPrice: p.number(),
    page: p.number(1),
    tags: p.array<string>([]),
    inStock: p.boolean(false)
});

export const load: PageServerLoad = async ({ url }) => {
    // Validate params
    const { params, errors } = validateQueryParameters(schema, url.searchParams);

    // params is fully typed and contains defaults for invalid/missing values
    // errors contains any validation errors (but params still has fallback values)

    // Fetch data using validated params
    const products = await db.products.findMany({
        where: {
            category: params.category !== 'all' ? params.category : undefined,
            price: {
                gte: params.minPrice ?? undefined,
                lte: params.maxPrice ?? undefined
            },
            tags: params.tags.length > 0 ? { some: params.tags } : undefined,
            inStock: params.inStock || undefined
        },
        skip: (params.page - 1) * 20,
        take: 20
    });

    return {
        products,
        filters: params,
        validationErrors: errors
    };
};

Basic Backend Example

// src/routes/api/search/+server.ts
import { validateQueryParameters, p } from 'svelte-url-search-params';
import { json } from '@sveltejs/kit';
import { z } from 'zod';
import type { RequestHandler } from './$types';

// Define your schema using Zod object
const schema = z.object({
    search: p.string(''),
    page: p.number(1),
    tags: p.array<string>([]),
    enabled: p.boolean(false)
});

export const GET: RequestHandler = async ({ url }) => {
    // Validate and parse URL search params
    const { params, errors } = validateQueryParameters(schema, url.searchParams);

    // params is fully typed and contains defaults for invalid/missing values
    // errors contains any validation errors (but params still has fallback values)

    const results = await searchDatabase({
        search: params.search,
        page: params.page,
        tags: params.tags,
        enabled: params.enabled
    });

    return json({ results, params, errors });
};

Important Notes for Backend Usage

  • validateQueryParameters() returns { params, errors } where params always contains valid values (using defaults for invalid/missing params)
  • Validation errors array contains any issues, but doesn't throw - you can handle them as needed
  • The function accepts a Zod object schema built with p.* helpers
  • All the same parameter types work: p.string(), p.number(), p.array(), p.enum(), p.integer(), p.boolean(), p.object()
  • Type inference works automatically - params is fully typed based on your schema

How It Works

  1. Initialization: Reads current URL search params on mount
  2. Reactive Reading: params.key parses the URL value using the Zod schema
  3. Reactive Writing: params.key = value updates the URL via goto()
  4. Auto-sync: Uses SvelteURLSearchParams for reactivity with Svelte 5
  5. Deduplication: Arrays automatically remove duplicate values

Troubleshooting

Values not updating

Make sure you're using Svelte 5 and SvelteKit 2. The library relies on Svelte 5's reactivity system.

Array duplicates

The library automatically removes duplicates from arrays. If you see duplicates, make sure you're using the latest version.

Type errors

Ensure your TypeScript version is up to date and that you're using the correct generic types:

// ✅ Correct
tags: p.array<string>([]);
ids: p.array<number>([]);

// ❌ Wrong - missing type argument
tags: p.array([]);

License

MIT

Contributing

Contributions are welcome! Please open an issue or PR on GitHub.

Support

Top categories

Loading Svelte Themes