tooinfinity / inertia-datatables
Server-side DataTables for Laravel + Inertia 3 with React, TanStack Table v8, and shadcn/ui
Package info
github.com/tooinfinity/inertia-datatables
Language:TypeScript
pkg:composer/tooinfinity/inertia-datatables
Fund package maintenance!
Requires
- php: ^8.4
- illuminate/contracts: ^12.0||^13.0
- illuminate/database: ^12.0||^13.0
- illuminate/support: ^12.0||^13.0
- inertiajs/inertia-laravel: ^3.0
Requires (Dev)
- larastan/larastan: ^3.9
- laravel/pao: ^1.0
- laravel/pint: ^1.29
- orchestra/testbench: ^10.0||^11.0
- pestphp/pest: ^4.6||^5.0
- pestphp/pest-plugin-laravel: ^4.1||^5.0
- pestphp/pest-plugin-rector: ^5.0
- pestphp/pest-plugin-type-coverage: ^4.0||^5.0
- phpstan/extension-installer: ^1.4
- rector/rector: ^2.6
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Server-side DataTables for Laravel + Inertia 3 with React 19, TanStack Table v9, and shadcn/ui.
Installation
Backend (Laravel)
composer require tooinfinity/inertia-datatables
Publish the configuration file:
php artisan vendor:publish --tag="inertia-datatables-config"
Frontend (React + shadcn/ui)
Install the DataTable components via the shadcn registry:
# For Laravel projects (installs to resources/js/components/) npx shadcn@latest add https://raw.githubusercontent.com/tooinfinity/inertia-datatables/main/public/r/data-table.json # Or from local path if cloning the repo npx shadcn@latest add ./path/to/inertia-datatables/public/r/data-table.json
This will install the complete DataTable feature into your Laravel application:
resources/js/components/data-table/data-table.tsxresources/js/components/data-table/data-table-toolbar.tsxresources/js/components/data-table/data-table-pagination.tsxresources/js/components/data-table/data-table-column-header.tsxresources/js/components/data-table/data-table-column-visibility.tsxresources/js/components/data-table/data-table-empty.tsxresources/js/components/data-table/use-data-table.tsresources/js/components/data-table/types.tsresources/js/components/ui/table.tsx
Required shadcn primitives (auto-installed as registry dependencies):
buttoninputdropdown-menuselectcheckboxtableskeleton
Required npm dependency (auto-installed):
@tanstack/react-table
Configuration
The package uses config/inertia-datatables.php:
return [ 'default_per_page' => 25, 'max_per_page' => 100, 'per_page_options' => [10, 25, 50, 100], 'search' => [ 'debounce' => 300, ], 'query' => [ 'page' => 'page', 'per_page' => 'per_page', 'search' => 'search', 'searches' => 'searches', 'sort' => 'sort', 'filters' => 'filters', ], ];
Backend Usage
With Eloquent Builder
use TooInfinity\InertiaDataTables\Column; use TooInfinity\InertiaDataTables\DataTable; public function index() { $users = DataTable::query(User::query()) ->columns([ Column::make('name') ->label('Name') ->searchable() ->sortable(), Column::make('email') ->label('Email') ->searchable() ->sortable(), Column::make('status') ->label('Status') ->filterable() ->sortable(), Column::make('created_at') ->label('Created') ->sortable(), ]) ->defaultSort('created_at', 'desc') ->handle(); return inertia('Users/Index', [ 'users' => $users, ]); }
With Query Builder
use Illuminate\Support\Facades\DB; use TooInfinity\InertiaDataTables\Column; use TooInfinity\InertiaDataTables\DataTable; public function index() { $users = DataTable::query(DB::table('users')) ->columns([ Column::make('name')->searchable()->sortable(), Column::make('email')->searchable()->sortable(), Column::make('status')->filterable()->sortable(), Column::make('created_at')->sortable(), ]) ->defaultSort('created_at', 'desc') ->handle(); return inertia('Users/Index', [ 'users' => $users, ]); }
Column Options
Column::make('name'); // Basic column Column::make('name')->label('Full Name'); // Custom label Column::make('email')->searchable(); // Enable global search Column::make('email')->sortable(); // Enable sorting Column::make('status')->filterable(); // Enable filtering (text input) Column::make('status')->filterable()->filterType('select')->filterOptions([ ['value' => 'active', 'label' => 'Active'], ['value' => 'inactive', 'label' => 'Inactive'], ]); // Select dropdown filter Column::make('is_admin')->filterable()->filterType('boolean'); // Yes/No filter Column::make('internal_id')->hidden(); // Hide from UI (but available in data)
Hidden Columns
The ->hidden() option marks a column as hidden in the UI only. It does not restrict API access:
- Hidden columns are still included in the response data
- Hidden columns can still be searched, sorted, and filtered if their capabilities are enabled
- The
hiddenflag is a UI hint for the frontend to not display the column by default - For true security restrictions, simply don't enable
->searchable(),->sortable(), or->filterable()on sensitive columns
Filter Types
| Type | Description | UI Control |
|---|---|---|
text (default) |
Free-text exact match | Text input |
select |
Predefined options | Checkbox list |
boolean |
True/false values | Yes/No checkboxes |
Request Parameters
The DataTable accepts these query parameters:
| Parameter | Description |
|---|---|
page |
Current page number |
per_page |
Items per page (max: max_per_page config) |
search |
Global search across searchable columns |
sort |
Sort columns (e.g., name,-created_at) |
filters |
Column filters (e.g., filters[status]=active) |
searches |
Column-specific search (e.g., searches[name]=john) |
Frontend Usage
import { DataTable } from '@/components/data-table/data-table'; type User = { id: number; name: string; email: string; status: string; created_at: string; }; export default function Index({ users }: { users: DataTableResponse<User> }) { return ( <DataTable data={users} dataPropName="users" /> ); }
Note: The
@/alias maps toresources/js/in Laravel projects. Components are installed toresources/js/components/via the shadcn registry.
The dataPropName prop specifies which Inertia page prop contains the DataTable result. This enables correct partial reloads when the table data changes. If omitted, it defaults to 'data'.
TypeScript Types
The package exports these types from components/data-table/types.ts:
interface DataTableColumn { name: string; label: string; sortable: boolean; searchable: boolean; filterable: boolean; hidden: boolean; filter_type?: 'text' | 'select' | 'boolean' | null; filter_options?: Array<{ value: string; label: string }>; } interface DataTableMeta { current_page: number; per_page: number; from: number | null; to: number | null; total: number; last_page: number; } interface DataTableQuery { page: number; per_page: number; search: string; sort: Array<{ column: string; direction: string }>; searches: Array<{ column: string; value: string }>; filters: Array<{ column: string; value: unknown }>; } interface DataTableConfig { debounce: number; per_page_options: number[]; } interface DataTableResponse<TData = unknown> { data: TData[]; meta: DataTableMeta; query: DataTableQuery; columns: DataTableColumn[]; config: DataTableConfig; }
Hook Options
The useDataTable hook accepts the following options:
interface DataTableOptions<TData = unknown> { data: DataTableResponse<TData>; dataPropName?: string; // Inertia prop name for partial reloads (default: 'data') }
The dataPropName option specifies which Inertia page prop contains the DataTable result. This enables correct partial reloads when the table data changes.
Security
The package implements a strict column allow-list. Only columns explicitly defined with ->searchable(), ->sortable(), or ->filterable() can be used in requests. Malicious requests like sort=users.password or filters[email]=admin@example.com are rejected with clear exceptions.
Query/Response Contract
Request Parameters (Client → Server)
| Parameter | Type | Description |
|---|---|---|
page |
integer |
Current page number (min: 1) |
per_page |
integer |
Items per page (min: 1, max: max_per_page config) |
search |
string |
Global search across all searchable columns |
sort |
string |
Comma-separated sort columns. Prefix with - for descending (e.g., name,-created_at) |
searches |
object |
Column-specific search (e.g., searches[name]=john&searches[email]=example) |
filters |
object |
Column filters (e.g., filters[status]=active&filters[is_admin]=true) |
Filter Value Semantics
| Value | Behavior |
|---|---|
"null" / "NULL" |
WHERE column IS NULL |
"not_null" / "NOT_NULL" |
WHERE column IS NOT NULL |
"*value" |
LIKE '%value' (wildcard prefix) |
"value*" |
LIKE 'value%' (wildcard suffix) |
"*value*" |
LIKE '%value%' (wildcard both sides) |
"true" / "false" |
Boolean exact match |
| Other strings | Exact match WHERE column = value |
Response Structure (Server → Client)
{
"data": [],
"meta": {
"current_page": 1,
"per_page": 25,
"from": 1,
"to": 25,
"total": 100,
"last_page": 4
},
"query": {
"page": 1,
"per_page": 25,
"search": "",
"sort": [],
"searches": [],
"filters": []
},
"columns": [
{
"name": "name",
"label": "Name",
"searchable": true,
"sortable": true,
"filterable": false,
"hidden": false,
"filter_type": null,
"filter_options": []
}
],
"config": {
"debounce": 300,
"per_page_options": [10, 25, 50, 100]
}
}
TypeScript Types
interface DataTableColumn { name: string; label: string; sortable: boolean; searchable: boolean; filterable: boolean; hidden: boolean; filter_type?: 'text' | 'select' | 'boolean' | null; filter_options?: Array<{ value: string; label: string }>; } interface DataTableMeta { current_page: number; per_page: number; from: number | null; to: number | null; total: number; last_page: number; } interface DataTableQuery { page: number; per_page: number; search: string; sort: Array<{ column: string; direction: string }>; searches: Array<{ column: string; value: string }>; filters: Array<{ column: string; value: unknown }>; } interface DataTableConfig { debounce: number; per_page_options: number[]; } interface DataTableResponse<TData = unknown> { data: TData[]; meta: DataTableMeta; query: DataTableQuery; columns: DataTableColumn[]; config: DataTableConfig; }
Optional: Reusable UsersDataTable Component
For projects with multiple user tables, extract a reusable component:
// resources/js/components/data-table/users-data-table.tsx import { DataTable } from './data-table'; interface User { id: number; name: string; email: string; status: string; role: string; is_admin: boolean; created_at: string; } const columns = [ { name: 'name', label: 'Name', searchable: true, sortable: true }, { name: 'email', label: 'Email', searchable: true, sortable: true }, { name: 'status', label: 'Status', filterable: true, sortable: true }, { name: 'role', label: 'Role', filterable: true, sortable: true }, { name: 'is_admin', label: 'Admin', filterable: true, filter_type: 'boolean', sortable: true }, { name: 'created_at', label: 'Created', sortable: true }, ] as const; export function UsersDataTable({ users }: { users: DataTableResponse<User> }) { return <DataTable data={users} dataPropName="users" />; }
// In your controller public function index() { $users = DataTable::query(User::query()) ->columns([ Column::make('name')->searchable()->sortable(), Column::make('email')->searchable()->sortable(), Column::make('status')->filterable()->sortable(), Column::make('role')->filterable()->sortable(), Column::make('is_admin')->filterable()->filterType('boolean')->sortable(), Column::make('created_at')->sortable(), ]) ->defaultSort('created_at', 'desc') ->handle(); return inertia('Users/Index', ['users' => $users]); }
Compatibility
| Laravel | PHP | Testbench | Status |
|---|---|---|---|
| 12.x | 8.4+ | 10.x | ✅ Supported |
| 13.x | 8.4+ | 11.x | ✅ Supported |
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please review our contributing guide to get started.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
Inertia Datatables is open-sourced software licensed under the MIT license.