Inertia Table
A powerful data table component.
By purchasing Inertia Start, you also get a license for Inertia Table, our premium Vue.js data table component built on top of TanStack Table.
If you don't need the full starter kit, you can also buy Inertia Table separately.
See Inertia Table in actionSort, filter, paginate and toggle columns on a live table with real data.
Capabilities
- Sorting: Click a column header to cycle through ascending, descending and unsorted. Right-click (or long-press on touch devices) opens a context menu with explicit Asc, Desc, Remove sorting and Hide actions. Sorting happens server-side through a
sortquery param, so it applies to the whole dataset and not only the current page. Enable it per column withenableSortingin yourColumnDef, set an initial order with thedefaultSortingprop, and allow sorting on several columns at once withenableMultiSort. - Filtering: Two complementary ways to narrow results, both sent server-side as
filter[...]query params. Thesearchprop renders a text input for free-text search, and thefiltersprop renders faceted dropdowns with predefined options. Each filtered attribute must be declared inallowedFilterson the backend. - Pagination: Built on Laravel Pagination — pass a paginator (or an API resource collection) and the table renders page links and a page-size selector, driven by the
pageandper_pagequery params. Customize the available page sizes with thepageSizesprop. - Visibility: A dropdown lets users show or hide columns, so dense tables stay readable on smaller screens. Mark a column as hideable with
enableHidingin itsColumnDef; the toggle itself is controlled by theenableColumnToggleprop. - Selectable: Set
enableRowSelectionto add checkboxes to each row plus a "select all" checkbox in the header. Access the selected rows from the parent component through the exposed TanStacktableinstance (getSelectedRowModel()) to build your own bulk actions such as deleting or exporting.
Sorting, filtering and pagination state live in the URL, so a filtered and sorted view stays intact across page reloads and can be shared or bookmarked. Use the name prop to prefix the query params when several tables live on the same page.
Usage
Backend
Inertia Table integrates seamlessly with Spatie Query Builder to handle filtering and sorting on the server side efficiently.
Example
For our example, we will return a paginated result of users using Spatie Query Builder:
use App\Models\User;
use Illuminate\Http\Request;
use Spatie\QueryBuilder\QueryBuilder;
public function index(Request $request)
{
$perPage = $request->integer('per_page', 10);
$perPage = in_array($perPage, [10, 25, 50, 100], true) ? $perPage : 10;
$users = QueryBuilder::for(User::class)
->allowedFilters('name', 'email')
->allowedSorts('email', 'name', 'created_at')
->paginate($perPage)
->withQueryString();
return Inertia::render('Users/Index', [
'users' => $users,
]);
}Inertia Table also supports API resources. So, you could also return a paginated resource collection:
use App\Http\Resources\UserResource;
use App\Models\User;
use Illuminate\Http\Request;
use Spatie\QueryBuilder\QueryBuilder;
public function index(Request $request)
{
$perPage = $request->integer('per_page', 10);
$perPage = in_array($perPage, [10, 25, 50, 100], true) ? $perPage : 10;
$users = QueryBuilder::for(User::class)
->allowedFilters('name', 'email')
->allowedSorts('email', 'name', 'created_at')
->paginate($perPage)
->withQueryString();
return Inertia::render('Users/Index', [
'users' => UserResource::collection($users),
]);
}Frontend
InertiaTable.vue expects ColumnDef definitions and a paginated response from Laravel.
- In Inertia mode, pass your paginated data to
dataand the component handles server-side pagination, sorting, and filters via query params (page,per_page,sort, andfilter[...]). - In API mode, omit
dataand provide aurl(optionally afetchCallback) so the component can fetch with axios or your own fetcher.
Use search to render a search input (the input will be injected in a filter query param). When set to true, it writes to filter[search]; when set to a string, it writes to filter[<value>].
Use filters to define faceted filter dropdowns, and name to scope query params if multiple tables live on the same page (for example, users_sort, users_filter[status]).
Example
List users returned by our controller in our backend example using these settings:
- Display 3 columns:
email,name,created_at. - Display a search input that filters results by the
nameattribute (this requiresnameto be included inallowedFiltersin the controller’s query builder). - Allow sorting by
email,name,created_atcolumns (this requiresemail,name, andcreated_atto be included inallowedSortsin the controller’s query builder). - Allow hiding
namecolumn. - By default, display the most recent users first (sort by
created_atdescendant).
<script setup lang="ts">
import type { ColumnDef } from '@tanstack/vue-table';
import { h } from 'vue';
import InertiaTable from '@/components/inertia-table/InertiaTable.vue';
import type { InertiaTablePaginatedResource } from '@/lib/inertia-table/types';
interface User {
id: number;
name: string;
email: string;
created_at: string;
}
interface Props {
users: InertiaTablePaginatedResource<User>;
}
const props = defineProps<Props>();
const getColumns = (): ColumnDef<User>[] => {
return [
{
accessorKey: 'email',
enableSorting: true,
header: () => 'Email',
cell: ({ row }) => row.getValue('email'),
},
{
accessorKey: 'name',
enableHiding: true,
enableSorting: true,
header: () => 'Name',
cell: ({ row }) => row.getValue('name'),
},
{
accessorKey: 'created_at',
enableSorting: true,
header: () => 'Created At',
cell: ({ row }) => new Date(row.getValue('created_at')).toLocaleString(),
},
];
};
</script>
<template>
<InertiaTable
:columns="getColumns()"
:data="props.users"
:default-sorting="[{ id: 'created_at', desc: true }]"
search="name"
/>
</template>TanStack table instance
InertiaTable component exposes the underlying table TanStack instance.
From the parent component, you can attach a ref to your InertiaTable component to access TanStack methods on the table instance, as shown in the following example:
<script setup lang="ts">
import { computed, ref } from 'vue';
import InertiaTable from '@/components/inertia-table/InertiaTable.vue';
const tableRef = ref<InstanceType<typeof InertiaTable> | null>(null);
const selectedRows = computed(() => {
return tableRef.value?.table.getSelectedRowModel().rows.map((row) => row.original) ?? [];
});
</script>
<template>
<InertiaTable ref="tableRef" :enable-row-selection="true" />
</template>Sorting
Sorting is enabled by default (enableSorting) and is opt-in per column: set enableSorting: true on the columns you want to make sortable in your ColumnDef.
Sortable headers respond to two interactions:
- Left click cycles the column through ascending → descending → unsorted.
- Right click (long-press on touch devices) opens a context menu with explicit actions: Asc, Desc, Remove sorting (only when the column is currently sorted) and Hide (only when the column is hideable).
The state is serialized into a sort query param using the Spatie Query Builder convention, where a leading minus sign means descending — for example ?sort=-created_at. Changing the sorting resets the table to the first page.
Multi-column sorting
By default, sorting one column replaces the previous sorting. Set enableMultiSort to let users stack several columns instead:
<InertiaTable
:columns="getColumns()"
:data="props.users"
:enable-multi-sort="true"
/>Each sorted column is then appended to the sort query param in the order it was picked, comma-separated — for example ?sort=name,-created_at sorts by name ascending, then by creation date descending. Make sure every column involved is listed in allowedSorts in your controller's query builder.
Filters
To add filters, enable the feature using enableFiltering prop, and define your filters in the filters prop:
<InertiaTable
:columns="getColumns()"
:data="props.posts"
:enable-filtering="true"
:filters="[
{
columnId: 'category',
title: 'Categories',
options: [
{ value: 'cinema', label: 'Cinema' },
{ value: 'music', label: 'Music' },
{ value: 'tech', label: 'Tech' },
{ value: 'travel', label: 'Travel' },
],
},
]"
/>Row selection
You can enable row selection using the enableRowSelection prop on InertiaTable component:
<InertiaTable :enable-row-selection="true" ref="tableRef" />Then, you can retrieve the selected rows like this:
- Get all selected rows using
getSelectedRowModel():
// Get all selected rows (row objects)
const selectedRows = computed(() => {
return tableRef.value?.table.getSelectedRowModel().rows;
});
// Get the data of selected rows
const selectedItems = computed(() => {
return tableRef.value?.table.getSelectedRowModel().rows.map((row) => row.original) ?? [];
});- Get only the selected rows that are currently visible (after filtering), use
getFilteredSelectedRowModel():
const visibleSelectedRows = computed(() => {
return tableRef.value?.table.getFilteredSelectedRowModel().rows;
});For more information, refer to the TanStack Table Row Selection Guide.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
name? | string | undefined | Prefix query params to avoid collisions when multiple tables are present on the same page. |
data? | InertiaTablePaginatedData<TData> | InertiaTablePaginatedResource<TData> | null | undefined | Paginated data from Laravel. Required for Inertia mode. |
url? | string | undefined | Base URL to fetch from in API mode (used when data is not provided). |
fetchCallback? | (url: string) => Promise<InertiaTablePaginatedData<TData> | InertiaTablePaginatedResource<TData>> | undefined | Custom fetcher for API mode. Defaults to axios. |
columns | ColumnDef<TData>[] | undefined | Column definitions (TanStack Table). |
pageSizes? | number[] | [10, 25, 50, 100] | Page-size options for the paginator. |
preserveState? | boolean | true | Preserve Inertia page state on navigation. |
preserveScroll? | boolean | true | Preserve scroll position on navigation. |
enableColumnToggle? | boolean | true | Show column visibility toggle. |
enableFiltering? | boolean | true | Show search and faceted filters. |
enableSorting? | boolean | true | Enable server-side sorting (sort query param). |
enableMultiSort? | boolean | false | Allow sorting on several columns at once (comma-separated sort query param). |
enableRowSelection? | boolean | false | Show row selection checkboxes. |
defaultSorting? | SortingState | [] | Initial sorting before reading URL params. |
search? | true | string | undefined | Render search input; true uses filter[search], string uses filter[<value>] query param. |
filters? | InertiaTableFilter[] | undefined | Faceted filter definitions (filter[<columnId>] query param). |
labels? | InertiaTableLabels | search: "Search", reset: "Reset", selectAll: "Select all", selectRow: "Select row", noResults: "No results found." | UI text overrides. |
columnHeaderLabels? | InertiaTableColumnHeaderLabels | ascDirection: "Asc", descDirection: "Desc", hide: "Hide", removeSorting: "Remove sorting" | Text overrides for the column header context menu. |
Types and interfaces
import type { Component } from 'vue';
export interface InertiaTablePaginatedData<T> {
data: T[];
current_page: number;
from: number;
to: number;
last_page: number;
links: Array<{
url: string | null;
label: string;
active: boolean;
page: number | null;
}>;
prev_page_url: string | null;
next_page_url: string | null;
first_page_url: string;
last_page_url: string;
path: string;
per_page: number;
total: number;
}
export interface InertiaTablePaginatedResource<T> {
data: T[];
links: {
first: string;
last: string;
next: string | null;
prev: string | null;
};
meta: {
current_page: number;
from: number;
to: number;
last_page: number;
links: Array<{
url: string | null;
label: string;
active: boolean;
page: number | null;
}>;
path: string;
per_page: number;
total: number;
};
}
export interface InertiaTableFilterOption {
value: string;
label: string;
icon?: Component;
}
export interface InertiaTableFilter {
columnId: string;
title: string;
icon?: Component;
options: InertiaTableFilterOption[];
}Advanced usage and guides
For advanced usage or to get more information on all the capabilities of TanStack Table, refer to their documentation.