Skip to content

Advanced Nuqs Table

Rule-based filtering with URL state persistence for shareable and bookmarkable table views.

A worked example of pushing every piece of filter, sort, and pagination state into the URL via nuqs, so a complex filtered view can be copy-pasted into a teammate’s browser and look identical. Pairs the rule-builder filter UI with shareable, bookmarkable links — refresh-safe and back-button-friendly.

Open in
Product Name
Category
Brand
Price
Stock
Rating
In Stock
Release Date
iPhone 15 ProElectronicsapple
$999.00
45
5
Yes7/10/2026
Galaxy S24 UltraElectronicssamsung
$1199.00
32
5
Yes7/5/2026
Air Jordan 1Sportsnike
$170.00
8
4
Yes6/20/2026
Ultraboost 23Sportsadidas
$190.00
15
4
Yes5/26/2026
PlayStation 5Electronicssony
$499.00
0
5
No7/15/2025
OLED C3 TVElectronicslg
$1499.00
12
5
Yes4/16/2026
XPS 15 LaptopElectronicsdell
$1899.00
20
4
Yes3/17/2026
Spectre x360Electronicshp
$1599.00
18
4
Yes6/30/2026
MacBook Pro 16Electronicsapple
$2499.00
25
5
Yes6/15/2026
Galaxy Book3Electronicssamsung
$1399.00
14
4
Yes1/16/2026
Current Table State (URL Synced)
All state is persisted in the URL and survives page refreshes
Current URL:No query params
URL Length:40 chars
Filter Mode:standard
Global Filter Type:String
Search Query:None
Total Items:15
Active Filters:0
Column Filters Count:0
Join Logic:and
Sorting:None
Page:1 (Size: 10)
Hidden Columns:0
View Full State Object
Enhanced Filters:
No enhanced filters
URL Filters (AND logic):
[]
Inline Filters:
[]
Global Filter (OR logic / Search):
""
Filter Stats:
{
  "totalFilters": 0,
  "hasAndFilters": false,
  "hasOrFilters": false,
  "effectiveJoinOperator": "and",
  "activeFilters": 0,
  "currentMode": "standard"
}
URL Pagination:
{
  "pageIndex": 0,
  "pageSize": 10
}
URL Sorting:
[]
Search Query:
""

💡 Tip: Try adding filters, sorting, or changing pages, then copy the URL and paste it in a new tab. All your table state will be preserved!

The Advanced Nuqs Table demonstrates rule-based filtering with URL state persistence using nuqs. This example shows how to sync table state (filters, pagination, sorting) with URL parameters for shareable and bookmarkable table views.

Install the DataTable core and add-ons for this example:

pnpm dlx shadcn@latest add @niko-table/data-table @niko-table/data-table-pagination @niko-table/data-table-search-filter @niko-table/data-table-view-menu @niko-table/data-table-sort-menu @niko-table/data-table-filter-menu @niko-table/data-table-column-sort @niko-table/data-table-column-faceted-filter @niko-table/data-table-column-slider-filter @niko-table/data-table-column-date-filter

Install additional dependencies:

pnpm add nuqs

Note: nuqs is a type-safe search params state manager that syncs table state (filters, pagination, sorting) with the URL, making it shareable and bookmarkable.

This example also uses checkbox from Shadcn UI:

pnpm dlx shadcn@latest add checkbox

First time using @niko-table? See the Installation Guide to set up the registry.

For other add-ons or manual copy-paste, see the Installation Guide.

Before using the Advanced Nuqs Table, you need to wrap your app with NuqsAdapter. Follow the official Nuqs adapters documentation for setup instructions based on your framework:

  • Next.js App Router - Wrap {children} in app/layout.tsx
  • Next.js Pages Router - Wrap <Component> in pages/_app.tsx
  • React SPA (Vite, CRA, etc.) - Wrap <App /> in src/main.tsx
  • Remix - Wrap <Outlet /> in app/root.tsx
  • React Router v6/v7 - See adapter-specific instructions
  • TanStack Router - Wrap <Outlet /> in your root route

See the Nuqs adapters documentation for complete setup instructions and code examples for your specific framework.

We are going to build a table to show products with URL state persistence. Here’s what our data looks like:

type Product = {
id: string
name: string
category: string
brand: string
price: number
stock: number
rating: number
inStock: boolean
releaseDate: Date
}

Let’s start by building a table with URL state persistence.

First, we’ll define our columns with filter metadata.

columns.tsx
"use client"
import { DataTableColumnHeader } from "@/components/niko-table/components/data-table-column-header"
import { DataTableColumnTitle } from "@/components/niko-table/components/data-table-column-title"
import { DataTableColumnSortMenu } from "@/components/niko-table/components/data-table-column-sort"
import type { DataTableColumnDef } from "@/components/niko-table/types"
export type Product = {
id: string
name: string
category: string
brand: string
price: number
}
const categoryOptions = [
{ label: "Electronics", value: "electronics" },
{ label: "Clothing", value: "clothing" },
]
export const columns: DataTableColumnDef<Product>[] = [
{
accessorKey: "name",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: {
label: "Product Name",
variant: "text",
},
enableColumnFilter: true,
},
{
accessorKey: "category",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: {
label: "Category",
variant: "select",
options: categoryOptions,
},
enableColumnFilter: true,
},
{
accessorKey: "price",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: {
label: "Price",
variant: "number",
unit: "$",
},
enableColumnFilter: true,
},
]

Define parsers for URL state using nuqs:

import { parseAsInteger, parseAsJson, parseAsString } from "nuqs"
import type { SortingState } from "@tanstack/react-table"
import type { ExtendedColumnFilter } from "@/components/niko-table/types"
const tableStateParsers = {
pageIndex: parseAsInteger.withDefault(0),
pageSize: parseAsInteger.withDefault(10),
sort: parseAsJson<SortingState>(value => value as SortingState).withDefault(
[],
),
filters: parseAsJson<ExtendedColumnFilter<Product>[]>(
value => value as ExtendedColumnFilter<Product>[],
).withDefault([]),
search: parseAsString.withDefault(""),
}

Next, we’ll connect the table state to URL parameters.

advanced-nuqs-table.tsx
"use client"
import { useMemo } from "react"
import { NuqsAdapter } from "nuqs/adapters/react"
import {
parseAsInteger,
parseAsJson,
parseAsString,
useQueryStates,
} from "nuqs"
import type {
PaginationState,
SortingState,
ColumnFiltersState,
} from "@tanstack/react-table"
import { DataTableRoot } from "@/components/niko-table/core/data-table-root"
import { DataTable } from "@/components/niko-table/core/data-table"
import {
DataTableHeader,
DataTableBody,
DataTableEmptyBody,
} from "@/components/niko-table/core/data-table-structure"
import { DataTableToolbarSection } from "@/components/niko-table/components/data-table-toolbar-section"
import { DataTablePagination } from "@/components/niko-table/components/data-table-pagination"
import { DataTableSearchFilter } from "@/components/niko-table/components/data-table-search-filter"
import { DataTableViewMenu } from "@/components/niko-table/components/data-table-view-menu"
import { DataTableSortMenu } from "@/components/niko-table/components/data-table-sort-menu"
import { DataTableFilterMenu } from "@/components/niko-table/components/data-table-filter-menu"
import { DataTableColumnHeader } from "@/components/niko-table/components/data-table-column-header"
import { DataTableColumnTitle } from "@/components/niko-table/components/data-table-column-title"
import { DataTableColumnSortMenu } from "@/components/niko-table/components/data-table-column-sort"
import type {
DataTableColumnDef,
ExtendedColumnFilter,
} from "@/components/niko-table/types"
type Product = {
id: string
name: string
category: string
price: number
}
const columns: DataTableColumnDef<Product>[] = [
{
accessorKey: "name",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: {
label: "Product Name",
variant: "text",
},
enableColumnFilter: true,
},
{
accessorKey: "category",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: {
label: "Category",
variant: "select",
options: [
{ label: "Electronics", value: "electronics" },
{ label: "Clothing", value: "clothing" },
],
},
enableColumnFilter: true,
},
{
accessorKey: "price",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: {
label: "Price",
variant: "number",
unit: "$",
},
enableColumnFilter: true,
},
]
const tableStateParsers = {
pageIndex: parseAsInteger.withDefault(0),
pageSize: parseAsInteger.withDefault(10),
sort: parseAsJson<SortingState>(value => value as SortingState).withDefault(
[],
),
filters: parseAsJson<ExtendedColumnFilter<Product>[]>(
value => value as ExtendedColumnFilter<Product>[],
).withDefault([]),
search: parseAsString.withDefault(""),
}
function FilterToolbar({
filters,
onFiltersChange,
}: {
filters: ExtendedColumnFilter<Product>[]
onFiltersChange: (filters: ExtendedColumnFilter<Product>[] | null) => void
}) {
return (
<DataTableToolbarSection>
<DataTableToolbarSection className="px-0">
<DataTableSearchFilter placeholder="Search products..." />
<DataTableViewMenu />
</DataTableToolbarSection>
<DataTableToolbarSection className="px-0">
<DataTableSortMenu className="ml-auto" />
<DataTableFilterMenu
filters={filters}
onFiltersChange={onFiltersChange}
/>
</DataTableToolbarSection>
</DataTableToolbarSection>
)
}
function AdvancedNuqsTableContent({ data }: { data: Product[] }) {
// URL state management with nuqs
const [urlParams, setUrlParams] = useQueryStates(tableStateParsers, {
history: "replace",
})
// Convert URL state to TanStack Table format
const pagination: PaginationState = useMemo(
() => ({
pageIndex: urlParams.pageIndex,
pageSize: urlParams.pageSize,
}),
[urlParams.pageIndex, urlParams.pageSize],
)
const sorting: SortingState = useMemo(
() => urlParams.sort || [],
[urlParams.sort],
)
const columnFilters: ColumnFiltersState = useMemo(
() => urlParams.filters.map(filter => ({ id: filter.id, value: filter })),
[urlParams.filters],
)
const handleFiltersChange = (
filters: ExtendedColumnFilter<Product>[] | null,
) => {
setUrlParams({ filters: filters || [] })
}
return (
<DataTableRoot
data={data}
columns={columns}
config={{
enablePagination: true,
enableSorting: true,
enableMultiSort: true,
enableFilters: true,
}}
state={{
globalFilter: urlParams.search,
sorting,
columnFilters,
pagination,
}}
onGlobalFilterChange={search =>
setUrlParams({ search: search as string })
}
onSortingChange={sort => setUrlParams({ sort })}
onColumnFiltersChange={filters => {
const extendedFilters = filters.map(
f => f.value as ExtendedColumnFilter<Product>,
)
setUrlParams({ filters: extendedFilters })
}}
onPaginationChange={pagination => {
setUrlParams({
pageIndex: pagination.pageIndex,
pageSize: pagination.pageSize,
})
}}
>
<FilterToolbar
filters={urlParams.filters}
onFiltersChange={handleFiltersChange}
/>
<DataTable>
<DataTableHeader />
<DataTableBody>
<DataTableEmptyBody />
</DataTableBody>
</DataTable>
<DataTablePagination />
</DataTableRoot>
)
}
export function AdvancedNuqsTable({ data }: { data: Product[] }) {
return (
<NuqsAdapter>
<AdvancedNuqsTableContent data={data} />
</NuqsAdapter>
)
}

Use useQueryStates to sync table state with URL parameters:

import { useQueryStates } from "nuqs"
const [urlParams, setUrlParams] = useQueryStates(tableStateParsers, {
history: "replace",
})

Define parsers for each piece of state:

import { parseAsInteger, parseAsJson, parseAsString } from "nuqs"
const tableStateParsers = {
pageIndex: parseAsInteger.withDefault(0),
pageSize: parseAsInteger.withDefault(10),
sort: parseAsJson<SortingState>(value => value as SortingState).withDefault(
[],
),
filters: parseAsJson<ExtendedColumnFilter<Product>[]>(
value => value as ExtendedColumnFilter<Product>[],
).withDefault([]),
search: parseAsString.withDefault(""),
}

✅ Use Advanced Nuqs Table when:

  • You need shareable URLs with table state
  • Users should be able to bookmark filtered/sorted views
  • You want browser back/forward navigation support
  • Table state should persist across page refreshes
  • You need to sync state with server-side rendering

❌ Consider other options when:

  • URL state is not needed (use Advanced Filter Table)
  • You prefer simpler state management
  • You don’t need shareable/bookmarkable views