Built-in codecs
A codec converts between a parsed query value and a typed value. It pairs parse (query to value) with serialize (value to query) in one object. vuqs ships codecs for the common shapes; for anything else, build your own.
import { codecs } from '@vuqs/core'
codecs.string // a ready-made codec
codecs.arrayOf(codecs.integer) // a factory: call it to get a codecParsed query values
Numeric and boolean codecs accept native values returned by the adapter, applying their validation and transformations directly. String codecs, string literals, and ISO date codecs require text. Scalar codecs consider the first array item; arrayOf processes each item and json reads the complete value.
codecs.integer.parse(42) // 42
codecs.boolean.parse(false) // false
codecs.index.parse(1) // 0
codecs.arrayOf(codecs.integer).parse([1, '2']) // [1, 2]
codecs.json<{ id: number }>().parse({ id: 1 }) // { id: 1 }Invalid input parses as absent
Every codec's parse returns undefined when the key is missing or invalid. No codec throws on bad input: a stale or hand-edited URL degrades to the default.
const page = useQueryState('page', codecs.integer.withDefault(1))
// ?page=42 → 42
// ?page=banana → 1 (invalid → undefined → default)
// (no ?page) → 1 (absent → undefined → default)Codecs
String
Reads a non-empty query string. Empty or whitespace-only values parse as absent.
useQueryState('q', codecs.string) // string | undefined
useQueryState('q', codecs.string.withDefault('')) // stringNumbers
Integer
Reads an integer from a number or base-10 string. Fractional, non-numeric, and non-finite input parses as absent; serializing truncates toward zero.
useQueryState('page', codecs.integer.withDefault(1)) // ?page=2Float
Reads a finite number from a number or numeric string. Invalid input parses as absent.
useQueryState('ratio', codecs.float) // ?ratio=1.5Hex
A non-negative hexadecimal integer. Serializing pads to an even length. Numeric query values retain hexadecimal interpretation through their decimal text: codecs.hex.parse(10) and codecs.hex.parse('10') both return 16.
useQueryState('color', codecs.hex) // ?color=ff8800 → 16746496Index
A 1-based index in the URL mapped to a 0-based value in your state, ideal for one-based page numbers over a zero-based array:
const page = useQueryState('page', codecs.index.withDefault(0))
// ?page=1 → 0, ?page=2 → 1, page.value = 2 → ?page=3Boolean
Reads booleans or the strings 'true' and 'false'. Anything else parses as absent.
useQueryState('archived', codecs.boolean.withDefault(false)) // ?archived=trueLiterals
literal accepts a homogeneous list of strings or numbers. Mixed lists and values of other types throw TypeError when creating the codec. Inline lists infer their literal union without as const.
String literal
Constrains a string to a fixed set. Anything outside the set parses as absent.
const sort = useQueryState(
'sort',
codecs.literal(['asc', 'desc']).withDefault('asc'),
)
// ^? QueryStateRef<'asc' | 'desc'>Numeric literal
Pass a list of numbers to parse numeric literals. Numeric strings such as '01' and '1.0' resolve to 1 when it is allowed.
const perPage = useQueryState('perPage', codecs.literal([10, 20, 50]))
// ^? QueryStateRef<10 | 20 | 50 | undefined>Enums
Constrains a value to the members of a TypeScript enum. Pass the enum itself: unlike literal, you never restate the values or wrap them in Object.values, and the value type is inferred from the enum.
enum Status {
Active = 'active',
Archived = 'archived',
}
const status = useQueryState('status', codecs.enum(Status).withDefault(Status.Active))
// ^? QueryStateRef<Status>
// ?status=archived → Status.ArchivedIt also accepts numeric and heterogeneous enums. A numeric member round-trips through its number, not its name (Level.High ⇄ ?level=2), and anything outside the enum parses as absent. Native numbers match numeric members; they are not converted into string members.
enum Level {
Low,
Medium,
High,
}
useQueryState('level', codecs.enum(Level)) // ?level=2 → Level.HighFixed set without an enum
For an inline set of strings or numbers, use literal. A plain as const object works with enum too, narrowing to its value union.
Dates
Three date codecs return a Date, differing only in their wire format. All compare by timestamp (valueOf), so equality is exact, and each parses as absent on invalid input.
| Codec | URL form | Example |
|---|---|---|
isoDateTime | full ISO-8601 | ?d=2026-06-22T10:00:00.000Z |
isoDate | YYYY-MM-DD (midnight UTC) | ?d=2026-06-22 |
timestamp | milliseconds since epoch | ?t=1719014400000 |
ISO date and time
isoDateTime reads any string Date accepts and serializes with toISOString().
const since = useQueryState('since', codecs.isoDateTime)
// ^? QueryStateRef<Date | undefined>
// ?since=2026-06-22T10:00:00.000ZISO date
isoDate keeps only the calendar day. It reads the leading YYYY-MM-DD of the text as midnight UTC and serializes the UTC date, so the time of day is dropped.
const from = useQueryState('from', codecs.isoDate)
// ?from=2026-06-22 → 2026-06-22T00:00:00.000ZTimestamp
timestamp reads an integer number of milliseconds since the epoch. Fractional or non-numeric input parses as absent.
const updated = useQueryState('updated', codecs.timestamp)
// ?updated=1719014400000Arrays
arrayOf wraps another codec to handle a list. A scalar query value is treated as a single-item array, items the inner codec rejects are dropped, and an empty result parses as absent.
const tags = useQueryState('tags', codecs.arrayOf(codecs.string).withDefault([]))
// ?tags=vue&tags=urls → ['vue', 'urls']
const ids = useQueryState('ids', codecs.arrayOf(codecs.integer))
// ?ids=1&ids=2&ids=banana → [1, 2] (the invalid item is dropped)Arrays in the URL
arrayOf uses repeated keys (?tags=a&tags=b), which the default vue-router parser and the browser History API adapter parse into an array. For nested objects, see Adapters.
JSON
Reads JSON text or an already-parsed query value and serializes with JSON.stringify. Objects and arrays are read in full, including empty structures. Nullish query nodes, non-finite numeric nodes, and invalid JSON text parse as absent.
The text "null" decodes to the JSON value null. Declare it in T when needed, for example codecs.json<{ id: number } | null>(). A native null query node parses as absent.
Pass a callback or a synchronous Standard Schema as validate. It receives the complete decoded or already-parsed value once. The callback return value or schema output is the codec result. Validation issues and throws parse as absent. Without validate, the value is accepted as T without schema validation.
import { z } from 'zod'
import { codecs, useQueryState } from '@vuqs/core'
const range = z.object({ min: z.number(), max: z.number() })
const priceRange = useQueryState('price', codecs.json({ validate: range }))
// ^? QueryStateRef<{ min: number, max: number } | undefined>
// ?price=%7B%22min%22%3A0%2C%22max%22%3A99%7D → { min: 0, max: 99 }Callbacks such as range.parse remain supported. The codec's parse throws TypeError when a Standard Schema returns a Promise. The schema output must remain serializable and be accepted on the next read. For transformations that require an inverse mapping, use a custom codec.
Use arrayOf(json()) for repeated JSON documents. json() treats an incoming array as the JSON value itself:
const raw = ['{"id":1}', '{"id":2}']
codecs.json<string[]>().parse(raw) // ['{"id":1}', '{"id":2}']
codecs.arrayOf(codecs.json<{ id: number }>()).parse(raw) // [{ id: 1 }, { id: 2 }]Keep JSON small
JSON values are URL-encoded and can grow long quickly. For a couple of params it is fine; for a large object, prefer several scalar keys or a composite param.
Defaults: .withDefault()
Every codec carries a .withDefault(value). It changes two things, covered in Concepts:
codecs.string // QueryStateRef<string | undefined>
codecs.string.withDefault('') // QueryStateRef<string>, and '' is dropped from the URL- Reads become
T: an absent key returns the default. - The default is omitted from the URL via
clearOnDefault.
Nullable writes: .nullable()
.nullable() returns a codec that accepts null and serializes it as absence. Parsing stays unchanged. Without a default, an absent param still reads as undefined; use .withDefault(null) when the empty state should be null:
const state = useQueryState('state', codecs.string.nullable().withDefault(null))
// ^? QueryStateRef<string | null>
state.set(null) // remove the param; state reads nullThe modifier preserves an existing default in either order:
codecs.integer.withDefault(1).nullable()
codecs.integer.nullable().withDefault(1)It also works with factory and custom codecs. On a JSON codec, nullable writes omit null instead of encoding the text "null".
Summary
| Codec | Type | URL example |
|---|---|---|
string | string | ?q=laptop |
integer | number | ?page=2 |
float | number | ?ratio=1.5 |
hex | number | ?color=ff8800 |
index | number (0-based) | ?page=1 → 0 |
boolean | boolean | ?archived=true |
literal([…]) | string union | ?sort=asc |
literal([10, 20, 50]) | number union | ?perPage=20 |
enum(E) | enum members | ?status=archived |
isoDateTime | Date | ?d=2026-06-22T10:00:00Z |
isoDate | Date | ?d=2026-06-22 |
timestamp | Date | ?t=1719014400000 |
arrayOf(c) | T[] | ?tags=a&tags=b |
json({ validate? }) | T | ?f=%7B…%7D |
For a type not covered here, build a custom codec.