nuqs-zod-adapter
v1.0.0
Published
A Nuqs adapter to safely bind Zod schemas with query parameters using type safety, debouncing and auto-reset support.
Maintainers
Readme
nuqs-zod-adapter
A nuqs adapter for using zod schemas and it's defaults together without having to repeat code unnecessarily
Fully compatible with zod schemas and defaults
How it will look like:
yourwebsite.com/search?title=cool+video&category=a
(All using Zod schemas & default values from them, 0 nuqs validation)
Features
- ✅ Infer types directly from Zod schema
- ✅ Debouncing feature (completely customizable)
- ✅ Optional reset of other query keys
- ✅ Compatible with all Nuqs options (
shallow,history, etc.) - ✅ Parses enums, numbers, booleans, unions, defaults... automatically
Install
npm install nuqs-zod-adapterUsage example
Let's say you want to create a filter for a search form which also includes category filtering that syncs with the URL using Nuqs, but with proper type safety, debouncing and optional reset of other keys (e.g., reset page on title change) and without having to duplicate the defaults
1. Define your Zod schema
import { z } from 'zod'
export const paramsFields = {
title: z.string().optional(),
category: z.enum(['a', 'b', 'c']).default('a'),
}Title: It's optional but not required, which means that it might not exist in the query
Category: Also optional, however its default value will be A when the query is empty
2. Use the hook to connect a param to the URL
Once your schema is defined, you can plug individual fields into the useSafeQueryStateFromZod hook. Here's how to do it for the category param:
import { useSafeQueryStateFromZod } from 'nuqs-zod-adapter'
import { paramsFields } from './schema'
const [category, setCategory] = useSafeQueryStateFromZod(
'category',
paramsFields.category,
{
delay: 200, // debounce delay (optional)
resetKeys: ['page'], // reset other query params when this changes
shallow: true // use shallow routing
// and all the nuqs options available
}
)✅ Syncs category with the query string
✅ Applies Zod validation and type inference
✅ Uses "a" as the default value if none is provided
✅ Debounces updates to avoid router spam
✅ Optionally resets other keys like "page"
3. Using it in your component
Once you have category and setCategory, you can bind them directly to a <select> input, radio buttons, or any other component like this:
<select
value={category}
onChange={(e) => setCategory(e.target.value as typeof category)}
className="border px-3 py-2 rounded"
>
<option value="a">Category A</option>
<option value="b">Category B</option>
<option value="c">Category C</option>
</select>⚠️ Make sure to cast e.target.value to the correct type (e.g. as typeof category) if TypeScript complains.
4. Combine multiple query params
You can use the hook multiple times, once per field, to build a full filter system connected to the URL
Category & Title together
const [title, setTitle] = useSafeQueryStateFromZod(
'title',
paramsFields.title,
{
delay: 300,
resetKeys: ['page'],
shallow: true
}
)
const [category, setCategory] = useSafeQueryStateFromZod(
'category',
paramsFields.category,
{
shallow: true
}
)Now they will be both synced, if you type a title on the form, it will display on the URL like this:
If there is no title:
yourwebsite.com
If there is a title:
yourwebsite.com?title=super+title
If there is a title AND a category:
yourwebsite.com?title=super+title&category=a
If there is only a category:
yourwebsite.com?category=a
Resetting the values
What if you want to reset all the values with a button? Well, it's as simple as adding a "onClick" function and using the exported setters to set the values to their defaults, here is an example:
<button
onClick={() => {
setTitle('')
setCategory('A') // or paramsFields.category._def.defaultValue() if you want to extract it
}}
className="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300"
>
Reset Filters
</button>As simple as that.
🛠 Contributing
Contributions are welcome! If you have improvements, bug fixes or new ideas, feel free to submit a pull request or open an issue.
