Skip to content

Repository files navigation

@andabove/vuqs

Type-safe URL query state for Vue 3 — like ref, but stored in the URL.

A Vue 3 port of nuqs by François Best.

Install

pnpm add @andabove/vuqs

Peer dependencies — install these if you don't already have them:

pnpm add vue@^3.5 vue-router@^4 @vueuse/router

Usage

useQueryState returns a Ref bound to a URL query parameter. Reading the ref gives you the current parsed value; writing the ref updates the URL.

import{useQueryState,parseAsString}from"@andabove/vuqs";// Ref<string | null> — null when ?q is absentconstsearch=useQueryState("q",parseAsString);// Readconsole.log(search.value);// 'hello' or null// Write — updates the URL automaticallysearch.value="hello";// Remove the param from the URLsearch.value=null;

With a default value

Use .withDefault(value) to get a Ref<T> (never null) and keep URLs clean when the value matches the default:

import{useQueryState,parseAsInteger}from"@andabove/vuqs";// Ref<number> — URL is clean when page === 1 (no ?page=1 in the URL)constpage=useQueryState("page",parseAsInteger.withDefault(1));page.value++;// ?page=2page.value=1;// removes ?page from the URLpage.value=null;// also removes ?page, resets to default (1)

History mode

By default, query updates replace the current history entry. Use mode: "push" to add a new entry so the back button restores the previous value:

import{useQueryState,parseAsString,parseAsStringLiteral}from"@andabove/vuqs";consttab=useQueryState("tab",parseAsString.withOptions({mode: "push"}));// Or chain it with a default:constsort=useQueryState("sort",parseAsStringLiteral(["asc","desc"]asconst).withDefault("asc").withOptions({mode: "push"}),);

Built-in parsers

ParserTypeNotes
parseAsStringstringIdentity — accepts any string
parseAsIntegernumberparseInt base 10, rounds on serialize
parseAsFloatnumberparseFloat, full precision
parseAsBooleanbooleanStrict: only "true" / "false"
parseAsStringLiteral(values)LiteralValidates against a readonly array of string literals
parseAsStringEnum(values)EnumValidates against a TypeScript string enum
parseAsArrayOf(parser, sep?)T[]Comma-separated by default; separator is URI-encoded inside values
parseAsJson(parseFn)TJSON with validator-agnostic parse function

parseAsStringLiteral example

import{useQueryState,parseAsStringLiteral}from"@andabove/vuqs";constcolors=["red","green","blue"]asconst;constcolor=useQueryState("color",parseAsStringLiteral(colors).withDefault("red"));// Ref<'red' | 'green' | 'blue'>

parseAsStringEnum example

import{useQueryState,parseAsStringEnum}from"@andabove/vuqs";enumDirection{Up="up",Down="down",}constdir=useQueryState("dir",parseAsStringEnum<Direction>(Object.values(Direction)).withDefault(Direction.Up),);

parseAsArrayOf example

import{useQueryState,parseAsArrayOf,parseAsInteger}from"@andabove/vuqs";// ?ids=1,2,3 → [1, 2, 3]constids=useQueryState("ids",parseAsArrayOf(parseAsInteger).withDefault([]));

parseAsJson example

parseAsJson is validator-agnostic — you supply any (raw: unknown) => T | null function. This works with Zod, Valibot, or plain conditionals.

import{useQueryState,parseAsJson}from"@andabove/vuqs";import*asvfrom"valibot";constschema=v.object({q: v.string(),page: v.number()});constfilters=useQueryState("filters",parseAsJson((raw)=>{constr=v.safeParse(schema,raw);returnr.success ? r.output : null;}),);
// With Zodimport{z}from"zod";constschema=z.object({q: z.string()});constfilters=useQueryState("filters",parseAsJson((raw)=>{constr=schema.safeParse(raw);returnr.success ? r.data : null;}),);

Custom parsers

Use createParser to build a custom parser that gets the full .withDefault() / .withOptions() builder API:

import{createParser,useQueryState}from"@andabove/vuqs";constparseAsHexColor=createParser({parse(query){return/^[0-9a-f]{6}$/i.test(query) ? query : null;},serialize(value){returnvalue;},});constcolor=useQueryState("color",parseAsHexColor.withDefault("ff0000"));

Feature comparison with nuqs

vuqs covers the core use case. Some nuqs features that are React/Next.js specific are not applicable in Vue.

Featurevuqsnuqs
useQueryState (single key)yesyes
useQueryStates (batched multi-key)plannedyes
Custom parsers via createParseryesyes
parseAsJson (validator-agnostic)yesyes (generic cast)
History push / replaceyesyes
shallow / server notifyn/aNext.js only
Server cache / createSearchParamsCachen/aNext.js only
createLoader / createSerializerplannedyes
useTransition loading statesn/aReact only
Testing adapterplannedyes

Development

This repo is a pnpm workspace:

PathDescription
packages/vuqsPublishable library
apps/playgroundInteractive demo (Vite + Vue Router)
pnpm install
pnpm dev # start playground at http://localhost:5173
pnpm test# run library unit tests
pnpm typecheck # typecheck library + playground
pnpm build # build library to packages/vuqs/dist
pnpm lint # oxlint
pnpm fmt:check # oxfmt (use pnpm fmt to fix)

The playground imports the library source directly (via Vite alias) so changes hot-reload without rebuilding.

Attribution

Inspired by nuqs by François Best (MIT). This is an independent Vue 3 port.

License

MIT — see LICENSE

About

Type-safe URL query state for Vue 3 - like ref, but stored in the URL

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages