vue-qs
vue-qs
Type-safe search params state manager for Vue 3. A port of nuqs
that uses Vue idioms: writable refs, v-model, reactive objects and a plugin.
📖 Documentation: https://iamsomraj.github.io/vue-qs/
const page = useQueryState('page', parseAsInteger.withDefault(1));
page.value++; // ?page=2
Why vue-qs?
Filters, search terms, tabs, pagination and sort orders are state your users expect to share, bookmark and restore with the Back button. Storing that state in the URL makes it all work, but parsing and serializing query strings by hand is tedious and error-prone.
vue-qs gives you URL state that feels like regular Vue state: a ref you can bind with
v-model, typed by parsers, and kept in sync with the router. It follows
nuqs, the popular React library, so its docs, patterns and ideas apply
here too.
Features
- 🧩 The nuqs API: the same parsers, options and behaviour, adapted to Vue
- 🔒 Type-safe: typed state from parsers, with
nullremoved by.withDefault() - ✍️ Writable refs: assign
.valueor usev-model;.set()for options and promises - 📦 Batched updates: changes in the same tick produce a single URL update
- ⏱️ Throttle & debounce: state is instant, URL writes are rate-limited
- 🧭 Adapters: Vue Router (and Nuxt), History API, testing, custom
- 🖥️ Server utilities:
createLoaderandcreateSerializershare your parsers - 🧪 Testable: an in-memory testing adapter
Installation
npm install vue-qs
Requires Vue 3.3+. vue-router (4.2+ or 5) is an optional peer dependency.
Setup
Install the plugin with an adapter:
import { createApp } from 'vue';
import { createVueQs } from 'vue-qs';
import { createVueRouterAdapter } from 'vue-qs/adapters/vue-router';
// Without a router: import { createHistoryAdapter } from 'vue-qs/adapters/history'
createApp(App)
.use(router)
.use(createVueQs({ adapter: createVueRouterAdapter(router) }))
.mount('#app');
For Nuxt, see the adapters guide.
Usage
<script setup lang="ts">
import { parseAsInteger, useQueryState } from 'vue-qs';
const name = useQueryState('name'); // Ref<string | null>
const count = useQueryState('count', parseAsInteger.withDefault(0)); // Ref<number>
</script>
<template>
<input v-model="name" placeholder="Your name" />
<button @click="count++">Count: {{ count }}</button>
<button @click="count = 0">Reset</button>
<!-- the default clears ?count -->
</template>
Updating with options
await count.set((c) => c + 1, { history: 'push', scroll: true });
await count.set(null); // removes ?count, count.value === 0
Several keys
import { toRefs } from 'vue';
import { parseAsFloat, useQueryStates } from 'vue-qs';
const coordinates = useQueryStates(
{ lat: parseAsFloat.withDefault(0), lng: parseAsFloat.withDefault(0) },
{ urlKeys: { lat: 'y', lng: 'x' } }
);
coordinates.lat = 48.85; // ?y=48.85
await coordinates.$set({ lat: 1, lng: 2 }); // one URL update
const { lat, lng } = toRefs(coordinates); // refs for v-model
Parsers
parseAsString, parseAsInteger, parseAsFloat, parseAsHex, parseAsIndex,
parseAsBoolean, parseAsTimestamp, parseAsIsoDateTime, parseAsIsoDate,
parseAsStringEnum, parseAsStringLiteral, parseAsNumberLiteral, parseAsArrayOf,
parseAsNativeArrayOf, parseAsJson (with a validator or Standard Schema), and
createParser for your own. See the parsers guide.
Options
| Option | Default | Description |
|---|---|---|
history |
'replace' |
'push' creates a history entry |
scroll |
false |
Scroll to top after the update |
clearOnDefault |
true |
Remove the key when the value equals the default |
limitUrlUpdates |
throttle(50) |
throttle(ms) or debounce(ms) |
shallow |
true |
Forwarded to custom adapters |
See the options guide.
Server
import { createLoader, createSerializer, parseAsInteger, parseAsString } from 'vue-qs/server';
const searchParams = { q: parseAsString.withDefault(''), page: parseAsInteger.withDefault(1) };
const load = createLoader(searchParams);
load(getQuery(event)); // { q: string; page: number }
const serialize = createSerializer(searchParams);
serialize('/search', { q: 'vue', page: 2 }); // '/search?q=vue&page=2'
Testing
import { createVueQs } from 'vue-qs';
import { createTestingAdapter } from 'vue-qs/adapters/testing';
const onUrlUpdate = vi.fn();
mount(Component, {
global: {
plugins: [
createVueQs({ adapter: createTestingAdapter({ searchParams: '?count=1', onUrlUpdate }) }),
],
},
});
Migrating from 0.1.x
0.2 replaces queryRef / queryReactive and codecs with useQueryState /
useQueryStates and parsers. See the
migration guide.
Community
vue-qs is an open-source project, and feedback shapes where it goes next.
- 💬 Questions: ask in Q&A
- 💡 Ideas and API feedback: share them in Ideas
- 🙌 Using vue-qs? Show it in Show and tell
- 🐛 Bugs: open an issue with a minimal reproduction
- 🛠️ Contributing: start with a good first issue and read the contributing guide
Everyone taking part is expected to follow the Code of Conduct.
If vue-qs is useful to you, a ⭐ on GitHub helps other Vue developers find it.
Credits
vue-qs is a port of nuqs by François Best.
License
MIT © Somraj Mukherjee
Related Packages
Minimal Vue 3 composables for keeping reactive state in Vue Router query paramet...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.2.3 | ^3.3.0 | MIT | |
| 0.2.2 | ^3.3.0 | MIT | |
| 0.2.1 | ^3.3.0 | MIT | |
| 0.2.0 | ^3.3.0 | MIT | |
| 0.1.18 | ^3.3.0 | MIT | |
| 0.1.17 | ^3.3.0 | MIT | |
| 0.1.16 | ^3.3.0 | MIT | |
| 0.1.15 | ^3.3.0 | MIT | |
| 0.1.14 | ^3.3.0 | MIT | |
| 0.1.13 | ^3.3.0 | MIT | |
| 0.1.12 | ^3.3.0 | MIT | |
| 0.1.10 | ^3.3.0 | MIT | |
| 0.1.9 | ^3.3.0 | MIT | |
| 0.1.8 | ^3.3.0 | MIT | |
| 0.1.7 | ^3.3.0 | MIT | |
| 0.1.6 | ^3.3.0 | MIT | |
| 0.1.5 | ^3.3.0 | MIT | |
| 0.1.4 | ^3.3.0 | MIT | |
| 0.1.3 | ^3.3.0 | MIT | |
| 0.1.2 | ^3.3.0 | MIT | |
| 0.1.1 | ^3.3.0 | MIT |
Pre-releases (7)
| Version | Released | Vue | License |
|---|---|---|---|
| 0.1.18-beta.7 | ^3.3.0 | MIT | |
| 0.1.18-beta.4 | ^3.3.0 | MIT | |
| 0.1.18-beta.2 | ^3.3.0 | MIT | |
| 0.1.16-beta.1 | ^3.3.0 | MIT | |
| 0.1.15-beta.2 | ^3.3.0 | MIT | |
| 0.1.15-beta.1 | ^3.3.0 | MIT | |
| 0.1.15-beta.0 | ^3.3.0 | MIT |