Type-safe URL query state management for Vue and Nuxt.
Documentation: https://goranton.github.io/vue-url-state/ (powered by VitePress) Nuxt guide: https://goranton.github.io/vue-url-state/guide/nuxt.html Examples: https://goranton.github.io/vue-url-state/guide/examples.html
vue-url-state helps manage filters, pagination, tabs, and other page state through typed URL query params.
Working with query params directly usually creates the same issues across pages:
- query params are string-based by default
- filter state gets duplicated between URL state and component state
router.replace/router.pushlogic is repeated everywhere- apply-mode filters require extra local buffering code
- default values and URL cleanup are easy to get wrong
npm install @goranton/vue-url-state
pnpm add @goranton/vue-url-state
yarn add @goranton/vue-url-stateInstall from npm using the scoped package name.
import {
arrayParam,
booleanParam,
defineQuerySchema,
enumParam,
numberParam,
stringParam,
} from '@goranton/vue-url-state';
const usersQuerySchema = defineQuerySchema({
search: stringParam(),
page: numberParam({ defaultValue: 1 }),
status: enumParam(['active', 'blocked', 'pending'] as const),
tags: arrayParam(stringParam()),
onlyWithErrors: booleanParam(),
});Inferred state type:
{
search: string | undefined;
page: number;
status: 'active' | 'blocked' | 'pending' | undefined;
tags: string[] | undefined;
onlyWithErrors: boolean | undefined;
}import { useQueryState } from '@goranton/vue-url-state';
const query = useQueryState(usersQuerySchema);
query.state.value;
await query.patch({ search: 'anton', page: 1 });
await query.remove(['status']);
await query.reset();stateis computed fromroute.querypatch()updates URL query values- default history mode is
replace - unknown query params are preserved by default
- default values are cleaned from URL by default
import { useQueryField, useQueryState } from '@goranton/vue-url-state';
const query = useQueryState(usersQuerySchema);
const search = useQueryField(query, 'search', {
resetOnChange: {
page: 1,
},
});
search.value = 'anton';useQueryFieldreturns a writable computed ref for a single field.- It is useful for
v-modelbindings in forms. - Setting the field updates URL state via
query.patch(). resetOnChangecan reset related fields (for examplepage: 1when search changes).onErrorcan be used to observe fire-and-forget helper failures.useQueryFieldis fire-and-forget because writable computed setters cannot be awaited directly.- For awaited navigation, explicit error handling, or fully controlled async flows, use
query.patch()directly.
import { useDebouncedQueryField, useQueryState } from '@goranton/vue-url-state';
const query = useQueryState(usersQuerySchema);
const search = useDebouncedQueryField(query, 'search', {
debounce: 300,
resetOnChange: {
page: 1,
},
});
search.value = 'anton';useDebouncedQueryFieldreturns a localRef.- It is useful for search inputs and similar frequently changing fields.
- The local value updates immediately.
- URL updates happen only after the debounce delay.
resetOnChangecan reset related fields (for examplepage: 1).onErrorcan be used to observe debounced fire-and-forget helper failures.- Debounce is intentionally not part of
useQueryState(). - For immediate updates, use
useQueryField(). - For explicit Apply-button flows, use
useQueryBuffer(). useDebouncedQueryFieldis also fire-and-forget because refs are not awaitable setters.
import { useQueryBuffer, useQueryState } from '@goranton/vue-url-state';
const query = useQueryState(usersQuerySchema);
const buffer = useQueryBuffer(query);
buffer.patch({ search: 'anton' });
if (buffer.isDirty.value) {
await buffer.apply();
}- draft changes stay local and do not update URL immediately
apply()writes the draft to URL throughquery.patch()reset()discards local draft editsclear()resets draft to default/empty stateapply()is awaitable and is the recommended controlled async flow.
import { deserializeQuery, serializeQuery } from '@goranton/vue-url-state';
const state = deserializeQuery(usersQuerySchema, {
page: '2',
status: 'active',
});
const raw = serializeQuery(usersQuerySchema, {
search: 'anton',
page: 1,
});- Schema:
defineQuerySchema - Params:
stringParam,numberParam,booleanParam,enumParam,arrayParam - Pure helpers:
deserializeQuery,serializeQuery,patchQuery,removeQueryKeys,resetQuery - Vue composables:
useQueryState,useQueryField,useDebouncedQueryField,useQueryBuffer
- Params without
defaultValueinfer asT | undefined. - Params with
defaultValueinfer asT. cleanDefaultsremoves default values from URL by default.preserveUnknownkeeps unknown query params by default.
patchQuery()andquery.patch()normalize full schema-owned query state, not only touched keys.- This keeps URLs canonical: invalid schema values are removed or defaulted, and default values are cleaned by default.
- Unknown query params are preserved by default.
- For non-array params receiving repeated query keys, the first value is used.
arrayParam()uses repeated query keys (for example?tags=vue&tags=nuxt).- Nested array params are not supported/documented.
- Early-stage project.
- Built for Vue 3 + Vue Router 4.
- Nuxt should work through Vue Router integration, but there is no Nuxt-specific module yet.
- API may still change before
1.0.