
Product
Announcing Socket Fix 2.0
Socket Fix 2.0 brings targeted CVE remediation, smarter upgrade planning, and broader ecosystem support to help developers get to zero alerts.
@chronicstone/vue-route-query
Advanced tools
Type-safe URL query parameter synchronization for Vue 3 with Zod validation
A powerful Vue 3 composable for type-safe URL query parameter synchronization with Zod validation, automatic state management, and intelligent default handling.
# npm
npm install @chronicstone/vue-route-query zod vue-router
# yarn
yarn add @chronicstone/vue-route-query zod vue-router
# pnpm
pnpm add @chronicstone/vue-route-query zod vue-router
# bun
bun add @chronicstone/vue-route-query zod vue-router
import { useRouteQuery } from '@chronicstone/vue-route-query'
import { z } from 'zod'
// Single value with key
const activeLayout = useRouteQuery({
key: 'layout',
schema: z.enum(['table', 'grid']),
default: 'table' // Won't appear in URL when value is 'table'
})
// Type: Ref<'table' | 'grid'>
const filters = useRouteQuery({
schema: {
search: z.string(),
status: z.array(z.string()),
date: z.object({
from: z.string(),
to: z.string()
})
},
default: {
search: '',
status: [],
date: { from: '', to: '' }
}
})
// Type: Ref<{ search: string; status: string[]; date: { from: string; to: string } }>
const userSettings = useRouteQuery({
key: 'settings', // Optional for object schemas - adds root prefix to all properties
schema: {
theme: z.string(),
notifications: z.boolean()
},
default: {
theme: 'light',
notifications: true
}
})
// URL: ?settings.theme=dark&settings.notifications=false
// Without key: ?theme=dark¬ifications=false
const sort = useRouteQuery({
schema: {
key: z.string(),
dir: z.enum(['asc', 'desc'])
},
default: { key: 'id', dir: 'asc' },
nullable: true // Allows the entire object to be null
})
// Type: Ref<{ key: string; dir: 'asc' | 'desc' } | null>
useRouteQuery<Schema, Nullable, Output>(config)
The main composable for managing URL query parameters.
Parameter | Type | Required | Description |
---|---|---|---|
schema | z.ZodType | Record<string, z.ZodType> | Yes | Zod schema for validation |
default | NonNullable<Output> | Yes | Default value (won't appear in URL when active) |
key | string | Required for single values | Root key for single value schemas or optional prefix for object schemas |
nullable | boolean | No | Whether the entire value can be null |
enabled | boolean | No | Enable/disable URL synchronization |
debug | boolean | No | Enable debug logging |
mode | 'push' | 'replace' | No | Navigation mode (default: 'replace') |
Ref<Output>
- A reactive reference to the synchronized state
Default Values: Default values are never shown in the URL. A parameter only appears in the URL when its value differs from the default.
Root Keys for Object Schemas: When using a key
with object schemas, it acts as a prefix for all properties:
// With key
useRouteQuery({ key: 'user', schema: { name: z.string() }, default: { name: '' } })
// URL: ?user.name=John
// Without key
useRouteQuery({ schema: { name: z.string() }, default: { name: '' } })
// URL: ?name=John
Nested Objects: Deep object structures are automatically flattened using dot notation:
// State
{ filters: { date: { from: '2024-01-01' } } }
// URL
?filters.date.from=2024-01-01
Arrays: Arrays are JSON stringified in the URL:
// State
{ tags: ['vue', 'typescript'] }
// URL
?tags=["vue","typescript"]
Multiple Instances: Multiple useRouteQuery
instances with the same key will stay synchronized. However, ensure they use compatible schemas to avoid conflicts.
Schema Validation: Don't use Zod's .default()
function - use the default
parameter instead.
Navigation Mode: The mode
parameter controls how router navigation occurs:
'replace'
(default): Updates the URL without creating a new history entry'push'
: Creates a new history entry for each updateWhen multiple instances update simultaneously, if any instance uses 'push'
, the router will use push mode for that batch of updates.
// Using push mode for filters to enable browser back/forward navigation
const filters = useRouteQuery({
schema: {
category: z.string(),
priceRange: z.object({
min: z.number(),
max: z.number()
})
},
default: {
category: '',
priceRange: { min: 0, max: 1000 }
},
mode: 'push' // Each filter change creates a history entry
})
// Using replace mode for preferences (default)
const preferences = useRouteQuery({
schema: {
view: z.enum(['list', 'grid']),
density: z.enum(['compact', 'comfortable'])
},
default: {
view: 'list',
density: 'comfortable'
}
// mode: 'replace' is the default
})
// If both update simultaneously and filters uses 'push',
// the router will use push for that update
const accountSettings = useRouteQuery({
key: 'account', // All properties will be prefixed with 'account.'
schema: {
profile: z.object({
name: z.string(),
email: z.string()
}),
preferences: z.object({
theme: z.enum(['light', 'dark']),
notifications: z.boolean()
})
},
default: {
profile: { name: '', email: '' },
preferences: { theme: 'light', notifications: true }
}
})
// URL structure:
// ?account.profile.name=John&account.profile.email=john@example.com&account.preferences.theme=dark
// Without the key, it would be:
// ?profile.name=John&profile.email=john@example.com&preferences.theme=dark
const filters = useRouteQuery({
schema: {
searchQuery: z.string().optional(),
filters: z.object({
statuses: z.array(z.string()),
categories: z.array(z.string()),
authorizationLabels: z.boolean(),
startDate: z.object({
from: z.string(),
to: z.string()
})
}),
quickFilters: z.record(z.string(), z.any())
},
default: {
searchQuery: '',
filters: {
statuses: [],
categories: [],
authorizationLabels: false,
startDate: { from: '', to: '' }
},
quickFilters: {}
}
})
// URL when changed from default:
// ?searchQuery=test&filters.statuses=["TO_CHECK_EP","VALIDATED"]&filters.categories=["19KZisAzakz3WESKnUy_C"]&filters.authorizationLabels=true&filters.startDate.from=2025-04-23&filters.startDate.to=2025-04-24
const sort = useRouteQuery({
schema: {
key: z.string(),
dir: z.enum(['asc', 'desc'])
},
default: { key: 'createdAt', dir: 'desc' },
nullable: true
})
// Can be set to null to disable sorting
sort.value = null
// URL when null: parameters removed
// URL when default: parameters removed
// URL when custom: ?key=name&dir=asc
const userPreferences = useRouteQuery({
schema: {
theme: z.enum(['light', 'dark', 'system']),
density: z.enum(['compact', 'comfortable', 'spacious']),
notifications: z.object({
email: z.boolean(),
push: z.boolean(),
frequency: z.enum(['instant', 'daily', 'weekly'])
})
},
default: {
theme: 'system',
density: 'comfortable',
notifications: {
email: true,
push: false,
frequency: 'daily'
}
},
enabled: shouldPersistPreferences.value // Conditionally enable URL sync
})
const pagination = useRouteQuery({
schema: {
pageSize: z.number(),
pageIndex: z.number()
},
default: {
pageSize: 20,
pageIndex: 1
},
mode: 'push' // Enable history for pagination
})
// Only appears in URL when different from default
// ?pageSize=50&pageIndex=3
Objects: Nested objects use dot notation
{ user: { settings: { theme: 'dark' } } }
// Becomes: ?user.settings.theme=dark
Arrays: Arrays are JSON stringified
{ tags: ['vue', 'ts'] }
// Becomes: ?tags=["vue","ts"]
Booleans: Represented as string values
{ active: true }
// Becomes: ?active=true
Numbers: Preserved as numeric strings
{ count: 42 }
// Becomes: ?count=42
Null/Undefined: Removed from URL entirely
The library uses a singleton GlobalQueryManager
that:
Works in all modern browsers that support:
The library is written in TypeScript and provides full type inference:
// Inferred type based on schema
const data = useRouteQuery({
schema: {
name: z.string(),
age: z.number().optional()
},
default: { name: '', age: undefined }
})
// data is Ref<{ name: string; age?: number }>
const filters = useRouteQuery({...})
// Reset to default (removes from URL)
filters.value = { ...defaultFilters }
const config = useRouteQuery({
schema: {
advanced: z.boolean(),
// Only used when advanced is true
customSettings: z.object({...}).optional()
},
default: {
advanced: false,
customSettings: undefined
}
})
// Both instances stay in sync
const userSettings1 = useRouteQuery({
key: 'settings',
schema: z.object({...}),
default: {...}
})
const userSettings2 = useRouteQuery({
key: 'settings', // Same key
schema: z.object({...}), // Must be compatible
default: {...}
})
Enable debug mode to see internal operations:
const data = useRouteQuery({
// ... other options
debug: true
})
MIT
Contributions are welcome! Please read our contributing guidelines before submitting a PR.
FAQs
Type-safe URL query parameter synchronization for Vue 3 with Zod validation
We found that @chronicstone/vue-route-query demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.ย It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Socket Fix 2.0 brings targeted CVE remediation, smarter upgrade planning, and broader ecosystem support to help developers get to zero alerts.
Security News
Socket CEO Feross Aboukhadijeh joins Risky Business Weekly to unpack recent npm phishing attacks, their limited impact, and the risks if attackers get smarter.
Product
Socketโs new Tier 1 Reachability filters out up to 80% of irrelevant CVEs, so security teams can focus on the vulnerabilities that matter.