useParsedQuery
The useParsedQuery composable parses and validates route query parameters using Zod schemas. It returns a reactive ref that automatically syncs with the URL as both the route changes and when you update the returned value.
usePagination & useSearchQuery that build on top of useParsedQuery for common use cases.Basic Usage
import { z } from 'zod'
const searchSchema = z.object({
q: z.string().default(''),
category: z.enum(['all', 'products', 'blog']).default('all'),
sort: z.enum(['newest', 'oldest', 'popular']).default('newest'),
})
export function useSearchFilters() {
const filters = useParsedQuery(searchSchema)
// filters.value is reactive and typed
// { q: string, category: 'all' | 'products' | 'blog', sort: 'newest' | 'oldest' | 'popular' }
return filters
}
<script setup lang="ts">
const filters = useSearchFilters()
// Fetch results based on current filters
const { data } = await useFetch('/api/search', {
query: filters,
})
</script>
<template>
<div>
<UInput v-model="filters.q" placeholder="Search..." />
<USelect v-model="filters.category" :options="['all', 'products', 'blog']" />
<USelect v-model="filters.sort" :options="['newest', 'oldest', 'popular']" />
<SearchResults :items="data" />
</div>
</template>
With Runtime Defaults
Override schema defaults with runtime values:
const paginationSchema = z.object({
page: z.coerce.number().min(1).default(1),
size: z.coerce.number().min(1).max(100).default(10),
})
// Override the schema's default size of 10 with 25
const query = useParsedQuery(paginationSchema, { size: 25 })
// query.value will be { page: 1, size: 25 } on initial load
Custom Route
By default, useParsedQuery uses the current route from useRoute(). You can pass a custom route instance:
const route = useRoute()
const query = useParsedQuery(mySchema, {}, { route })
How It Works
Parsing & Validation
The composable uses partialParse utility which validates each field independently:
- Valid data → Uses the parsed value
- Invalid data → Falls back to runtime defaults, then schema defaults
- Missing data → Uses runtime defaults, then schema defaults
This ensures the returned object always has valid, type-safe values.
URL Sync
- Route → State: When URL query params change, the composable parses and validates them, updating the reactive ref
- State → Route: When you mutate the returned ref, it automatically updates the URL via
navigateTo()
const filters = useParsedQuery(searchSchema)
// This will update the URL to ?category=products
filters.value.category = 'products'
Default Value Omission
Query parameters matching their default values are automatically excluded from the URL to keep it clean:
const schema = z.object({
page: z.coerce.number().default(1),
size: z.coerce.number().default(10),
})
const query = useParsedQuery(schema)
// If page=1 and size=10 (both defaults), URL will be just /page
// If page=2 and size=10, URL will be /page?page=2
// If page=1 and size=25, URL will be /page?size=25
Type Coercion
The composable respects Zod's type coercion. Use z.coerce to convert query string values:
const schema = z.object({
// Query strings are always strings, coerce to number
page: z.coerce.number().min(1).default(1),
// Boolean flags from query params
featured: z.coerce.boolean().default(false),
// Parse comma-separated values into array
tags: z.string().transform(s => s.split(',')).default([]),
})
const query = useParsedQuery(schema)
// URL: ?page=5&featured=true&tags=vue,nuxt
// query.value: { page: 5, featured: true, tags: ['vue', 'nuxt'] }
API Reference
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
zodSchema | z.ZodObject<S> | Yes | Zod schema defining the expected query parameters |
defaults | Partial<z.input<S>> | No | Runtime defaults that override schema defaults |
options.route | ReturnType<typeof useRoute> | No | Custom route instance (defaults to current route) |
Returns
Returns a reactive Ref containing the parsed and validated query parameters. The ref is deeply watched and automatically syncs changes back to the URL.
Implementation Details
Why Ref Instead of Computed?
The composable uses ref instead of computed for better control over side effects. With computed, you'd need to replace the entire object to trigger updates:
// With computed (not used) - triggers all watchers
parsedQuery.value = { ...parsedQuery.value, page: 1 }
// With ref (actual implementation) - surgical updates
parsedQuery.value.page = 1
This allows you to update individual properties without triggering unnecessary watchers on other properties.
Deep Watching
The composable sets up a deep watcher on the returned ref. When any nested property changes, it:
- Parses the new value through the Zod schema
- Filters out properties matching their default values
- Updates the URL via
navigateTo()with the filtered query params

