url()

url() wraps a default value and keeps the variable in the query string.

The result is a page whose state can be linked to, bookmarked and shared - filters, search terms, the current page number.

<script>
let search = url('')
</script>

<input bind:value={search} placeholder="Type, and watch the address bar" />
<p>Searching for: {search}</p>
output

Searching for:

The variable's name is the parameter name, so that writes ?search=hello. It reads the URL on mount, writes it on every assignment, and follows the back and forward buttons.

Writes use replaceState, so typing does not fill the history with one entry per keystroke.

Pass a name first to use a different parameter:

source
let searchQuery = url('q', '')   // ?q=
let currentPage = url('p', 1)    // ?p=

Types

The default value decides how the parameter is read back:

Default In the URL Read back as
url('') ?q=hello The string, unchanged
url(1) ?page=3 Number(). A blank or unparseable value falls back to the default.
url(false) ?dark=true True only for the exact text true
url([]) ?tags=a&tags=b Every value of that name, as an array
url({}) URL-encoded JSON JSON.parse(), falling back to the default if it fails

Clean URLs

A value equal to its default is removed from the URL, so a page sitting on its defaults has no query string at all rather than ?search=&page=1.

<script>
let page = url(1)

const totalPages = 10
</script>

<button disabled={page <= 1} onclick="page--">Previous</button>
<span>Page {page} of {totalPages}</span>
<button disabled={page>= totalPages} onclick="page++">Next</button>
output
Page 1 of 10

An empty string is a value, not an absence. A variable declared url('newest') and then cleared writes ?sort= and reads back '', so a cleared filter stays cleared across a reload. Only null and undefined cannot be put in a URL at all.

A key that appears twice

?a=1&a=2 resolves to its first value - "1" - and that is true of every scalar reader: a url() variable and window.route.query.a give the same answer, because both ask the platform's own URLSearchParams.get().

Use the array form when you want every repeat.

source
// URL: /list?a=1&a=2

let a = url('')      // "1"     - the first
let all = url([])    // ["1", "2"] - both, in URL order
route.query.a        // "1"     - the same rule

A repeated key is not exotic: a form with two same-named inputs, a link built by string concatenation, or a redirect that appends a param that was already there all produce one. The two spellings are still different URLs to the router, so navigating from ?a=1&a=2 to ?a=2&a=1 is a real navigation even though both read a the same way.

Text a URL cannot decode

% starts a two-hex-digit escape, so ?a=%zz is not valid encoding. JAF hands it back as the literal text "%zz" rather than throwing, and does the same for a path segment - /users/%zz gives route.params.id === "%zz".

Dev builds warn, naming the reader and the segment. Write %25 for a literal percent sign.