Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time

Tiny, state-based, advanced router for SvelteJS.

NPM version NPM downloads

A completely different approach of routing. State-based router suggests that routing is just another global state and History API changes are just an optional side-effects of this state.


  • Zero-config.
  • Just another global state.
  • It doesn't impose any restrictions on how to apply this state to the application.
  • Manipulate different parts of a state (path/query/hash) separately.
  • Automatic parsing of the query params, optional parsing path params.
  • Helpers to work with navigation, links, and even forms.


npm i svelte-pathfinder --save-dev
yarn add svelte-pathfinder

CDN: UNPKG | jsDelivr (available as window.Pathfinder)

<script src=""></script>

<!-- OR in modern browsers -->

<script type="module" src=""></script>

URL schema to state




  • path - represents path segments of the URL as an object.
  • query - represents query params of the URL as an object.
  • fragment - represents fragment (hash) string of URL.
  • state - represents state object associated with the new history entry created by pushState().
  • url - represents full URL string.
  • pattern - function to match path patterns to path.params and return boolean result.


  • goto - perform navigation to the next router state by URL.
goto(url: String, state?: Object)
  • back - perform navigation to the previous router state.
back(path?: String)
  • redirect - update current url without new history record.
redirect(url: String, state?: Object)
  • click - handle click event from the link and perform navigation to its targets.
click(event: MouseEvent)
  • submit - handle submit event from the GET-form and perform navigation using its inputs.
submit(event: SubmitEvent)


  • prefs - preferences object
    • sideEffect - manually disable/enable History API usage (changing URL) (default: auto).
    • hashbang - manually activate hashbang-routing.
    • basePath - set base path if web app is located within a nested basepath.
    • convertTypes - disable converting types when parsing query/path parameters (default: true).
    • nesting - number of levels when pasring nested objects in query parameters (default: 3).
    • array.format - format for arrays in query parameters (possible values: 'bracket' (default), 'separator').
    • array.separator - if format is separator this field give you speficy which separator you want to use (default: ',').

To change the preferences, just import and change them somewhere on top of your code:

import { prefs } from 'svelte-pathfinder';

prefs.convertTypes = false;
prefs.array.format = 'separator';


Changing markup related to the router state

{#if $pattern('/products/:id')} <!-- eg. /products/1 -->
    <ProductView productId={$} />
{:else if $pattern('/products')} <!-- eg. /products?page=2&q=Apple -->
    <ProductsList page={$} search={$query.params.q} />
    <NotFound path={$path.toString()} />

<Modal open={$fragment === '#login'}>
    <LoginForm />

    import { path, query, fragment, pattern } from 'svelte-pathfinder';

Changing logic related to the router state

<svelte:component this={page} />

    import { path, pattern } from 'svelte-pathfinder';
    import routes from './routes.js';
    $: page = routes.find((route) => $pattern(route.match)) || null;
    $: if ($path.toString() === '/admin' && ! isAuthorized) {
        $path = '/forbidden';

User input

<input bind:value={$query.params.q} type="search" placeholder="Find...">

    import { query } from 'svelte-pathfinder';

Use with the other stores

import { derived } from 'svelte/store';
import asyncable from 'svelte-asyncable';
import { path, query } from 'svelte-pathfinder';

// with regular derived store
export const productData = derived(path, ($path, set) => {
    if ($'/products/')) {
            .then(res => res.json())
}, {});

// with svelte-asyncable
export const productsList = asyncable(async $query => {
    const res = await fetch(`/api/products${$query.toString()}`)
    return res.json();
}, undefined, [ query ]);

Directly bind & assign values to stores

<input bind:value={$query.params.q} placeholder="Search product...">

<button on:click={() => $fragment = '#login'}>Login</button>

<a href="/products/10" on:click={e => $path = '/products/10'}>
    Product 10

Using helper click

Auto-handling all links in the application.

<svelte:window on:click={click} />

<nav class="navigate">
    <a href="/">Home</a>
    <a href="/products">Products</a>
    <a href="/about">About</a>

<!-- links below will be excluded from the navigation -->
<nav class="not-navigate">
    <a href="">External link</a>
    <a href="/products" target="_blank">Open in new window</a>
    <a href="/path/" download>Download pricing table</a>
    <a href="/" target="_self">Navigate with full page reload</a>
    <a href="/cart" on:click|preventDefault|stopPropagation={doSomething}>
        Just stop click event bubbling

    import { click } from 'svelte-pathfinder';

Using helpers goto and back

<button on:click={() => back()}>Back</button> 
<button on:click={() => goto('/cart?tab=overview')}>Open cart</button>

    import { goto, back } from 'svelte-pathfinder';

Using helper submit

<!-- handle GET-forms -->
<form on:submit={submit} action="/products" method="GET">
    <!-- all hidden fields will be propagated to $state by name attributes -->

    <input type="hidden" name="uid" value={$state.uid}>

    <!-- all visible fields will be propagated to $query by name attributes -->

    <input name="q" value={$query.params.q} placeholder="Title...">

    <select name="option" value={$query.params.option}>

    <!-- even pushed submit button will be propagated to $query by name attribute -->

    <button name="type" value="quick">Quick search</button>
    <button name="type" value="fulltext">Fulltext search</button>

    import { submit, query, state } from 'svelte-pathfinder';

Optional side-effect (changing browser history and URL)

Router will automatically perform pushState to browser History API and listening popstate event if the following conditions are valid:

  • router works in browser and global objects are available (window & history).
  • router works in browser which is support pushState/popstate.
  • router works in top-level window and has no parent window (eg. iframe, frame, object,

If any condition is not applicable, the router will work properly but without side-effect (changing URL).

hashbang routing (#!)

Router will automatically switch to hashbang routing in the following conditions:

  • History API is not available.
  • web app has launched under file: protocol.
  • initial path contain exact file name with extension.


MIT © PaulMaly


State-based router for Svelte 3






No releases published


No packages published