SvelteKit query parameters: read, update and validate
Resources
Examples checked: Svelte 5.55.0 / SvelteKit 2.55.0 · September 14, 2026
Read SvelteKit query parameters with url.searchParams in load, or page.url.searchParams from $app/state in a Svelte 5 component. To change the URL and load its data, copy the URL and navigate with goto. Changing the URL object alone does not perform a navigation.
This is a focused reference for reading, validating, setting and removing parameters. The examples were checked with Svelte 5.55.0 and SvelteKit 2.55.0. $app/state requires Svelte 5 and was introduced in SvelteKit 2.12; older applications use the $app/stores API.
Build a complete dashboard with URL filters and pagination if you need the whole data flow.
Read a query parameter in a component
For /query?q=hello&tag=svelte&tag=typescript, get("q") returns "hello" and getAll("tag") returns both tags. A missing key gives null; ?q= gives an empty string. Query values are strings, even when they look like numbers.
Create src/routes/query/+page.svelte:
<script lang="ts">
import { page } from '$app/state';
import { goto } from '$app/navigation';
const query = $derived(page.url.searchParams.get('q') ?? '');
const tags = $derived(page.url.searchParams.getAll('tag'));
const setQuery = async ({ value }: { value: string }) => {
const next = new URL(page.url);
const trimmed = value.trim();
if (trimmed) next.searchParams.set('q', trimmed);
else next.searchParams.delete('q');
next.searchParams.delete('page');
await goto(next, { keepFocus: true, noScroll: true });
};
</script>
<p>Query: {query || '(none)'}</p>
<p>Tags: {tags.join(', ') || '(none)'}</p>
<button onclick={() => setQuery({ value: 'Svelte & SvelteKit' })}>Set query</button>
<button onclick={() => setQuery({ value: '' })}>Clear query</button>Use $derived so the displayed values update after navigation. A top-level const that captures page.url.searchParams.get("q") only computes once for that component instance. Do not copy the old $page store syntax into an $app/state example.
SvelteKit $app/state reference
Set, remove and preserve parameters
The example copies page.url before editing it. set replaces the values for a key; delete removes that key. Existing tags and unrelated parameters survive. Clearing the search removes q, and changing it removes page so a new search starts at the first page.
URLSearchParams handles escaping. Passing "Svelte & SvelteKit" to set produces an encoded value; do not manually concatenate raw input into a query string or encode it twice. For repeated filters, use append instead of set when you want to add another value.
goto creates a browser history entry by default. Use { replaceState: true } when deliberately replacing the current entry, such as debounced typing, so Back does not step through every keystroke. The example uses explicit buttons and keeps the default history behavior. It requires JavaScript; ordinary links and GET forms are alternatives when the interaction must work without it.
Validate query parameters on the server
Create src/routes/query/+page.server.ts:
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
const load = (({ url }) => {
const rawPage = url.searchParams.get('page') ?? '1';
const pageNumber = Number(rawPage);
if (!/^\d+$/.test(rawPage) || !Number.isSafeInteger(pageNumber) || pageNumber < 1) {
error(400, 'Page must be a positive integer');
}
return { query: (url.searchParams.get('q') ?? '').trim(), pageNumber };
}) satisfies PageServerLoad;
export { load };Reject malformed page values before using them as offsets. Number("1.5") is a number, but not a valid page here; Number("") is zero. The digit check and safe-integer check reject those cases and values too large to represent reliably. Repeated page parameters use the first value because get returns the first match; reject duplicates explicitly if your endpoint needs a stricter contract.
Use the load event URL on the server, rather than window.location or importing page from $app/state. error(400, ...) produces an HTTP error response. Returning { status: 400, error: ... } as ordinary load data does not set that response status.
Why does changing a parameter rerun load?
SvelteKit tracks parameters accessed through url.searchParams.get, getAll and has in load. Navigation that changes a tracked parameter can rerun that load function. Here both q and page are dependencies. Reading url.search tracks the whole search string. Keep this access inside load rather than a detached callback.
A +page.server.ts load always executes on the server. During a client navigation, SvelteKit requests its result from the server; the function itself does not move into the browser. No invalidateAll call is needed for the URL changes in this example.
A few boundaries worth keeping
Query strings are visible and can end up in browser history, logs and shared links. Do not put access tokens, passwords or private project details in them. Treat every value as untrusted input. A project ID in the URL is not permission to read that project: scope the database query to the signed-in user.
Shallow routing with pushState or replaceState is for changes that do not perform a navigation. It is not a substitute for goto when server data must be loaded again. Reserve it for local presentation state such as an open modal.