manusiakemos/laravel-tanstack
| Install | |
|---|---|
composer require manusiakemos/laravel-tanstack |
|
| Latest Version: | 0.1.0 |
| PHP: | ^8.2 |
| License: | MIT |
| Last Updated: | May 27, 2026 |
| Links: | GitHub · Packagist |
Laravel DataTables for TanStack
Modern server-side datatable for Laravel, purpose-built for TanStack Table frontends (React, Vue, Svelte, Solid). Inspired by yajra/laravel-datatables but with a clean REST API instead of the legacy datatables.net protocol.
Use this when you have an Inertia (or any SPA) frontend, want server-side processing, and want to stop writing pagination + search + sort + filter boilerplate for every table.
Why this package
- Three-line endpoints. Whitelist columns, return the response. That's it.
- REST-style query string. Clean, browser-DevTools-friendly, easy to cache.
- Type-safe FE companion. Pairs with
@manusiakemos/laravel-tanstack-react(separate package). - Eloquent or Query Builder. Both supported, no extra setup.
- No jQuery, no datatables.net baggage.
Installation
composer require manusiakemos/laravel-tanstack
Publish the config (optional):
php artisan vendor:publish --tag=laravel-tanstack-config
Quick start
use Manusiakemos\LaravelTanstack\DataTable;
use App\Models\User;
class UserDataTableController
{
public function __invoke(Request $request)
{
return DataTable::for(User::query())
->searchable(['name', 'email'])
->sortable(['name', 'email', 'created_at'])
->filterable(['status', 'role']);
}
}
Register it on a web route so it has access to the session (for Inertia auth):
Route::get('/datatable/users', UserDataTableController::class)->middleware('auth');
Done. The controller returns a JsonResponse automatically because DataTable implements Responsable.
API protocol
Request
GET /datatable/users?
page=1&
per_page=25&
sort=name:asc,created_at:desc&
search=hafiz&
filter[status]=active&
filter[role][]=admin&filter[role][]=editor
| Param | Description |
|---|---|
page |
1-indexed page number. Defaults to 1. |
per_page |
Rows per page. Clamped to max_per_page config. |
sort |
Comma-separated column:direction pairs. |
search |
Global search term across searchable() columns. |
filter[col]=v |
Equals filter. |
filter[col][]=v1&filter[col][]=v2 |
whereIn filter. |
Response
{
"data": [
{ "id": 1, "name": "Hafiz", "email": "hafiz@example.com" }
],
"meta": {
"page": 1,
"per_page": 25,
"total": 1234,
"filtered": 89,
"last_page": 4
}
}
Features
Transform rows
DataTable::for(User::query()->with('role'))
->transform(fn ($user) => [
'id' => $user->id,
'name' => $user->name,
'role' => $user->role->name,
'status_label' => $user->status === 'active' ? 'Active' : 'Inactive',
]);
Or use an API Resource:
DataTable::for(User::query())->resource(UserResource::class);
Custom search
DataTable::for(User::query())
->search(fn ($q, $term) =>
$q->whereRaw('LOWER(name) LIKE ?', ['%'.strtolower($term).'%'])
->orWhereHas('role', fn ($r) => $r->where('name', 'like', "%{$term}%"))
);
Custom sort column
For computed or relation columns:
DataTable::for(User::query())
->sortable(['name', 'role_name'])
->orderColumn('role_name', fn ($q, $dir) =>
$q->orderBy(
Role::select('name')->whereColumn('roles.id', 'users.role_id'),
$dir
)
);
Custom filter
DataTable::for(User::query())
->filterColumn('created_between', fn ($q, $value) =>
$q->whereBetween('created_at', explode(',', $value))
);
Request: ?filter[created_between]=2024-01-01,2024-12-31
Authorization
DataTable::for(User::query())
->authorize(fn () => Gate::allows('viewAny', User::class));
Returns 403 if the closure returns false.
Default sort
DataTable::for(User::query())
->sortable(['created_at'])
->defaultSort('created_at', 'desc');
Skip total count
For very large tables where count(*) over the unfiltered set is expensive:
DataTable::for(User::query())->skipTotal();
The response will have meta.total = null; frontend should rely on filtered only.
Pagination limits
DataTable::for(User::query())
->defaultPerPage(50)
->maxPerPage(200);
Query builder macro
For one-liner usage:
return User::query()
->where('active', true)
->toDataTable()
->searchable(['name'])
->sortable(['name']);
Inertia + TanStack pattern
The recommended setup: let Inertia render the page shell (layout, auth state, navigation), and let the table component fetch its own data from a separate JSON endpoint.
// Page rendered by Inertia
import { useDataTable } from '@manusiakemos/laravel-tanstack-react'
function UsersIndex() {
const { table, loading } = useDataTable<User>({
endpoint: '/datatable/users',
columns: [
{ accessorKey: 'name', header: 'Name' },
{ accessorKey: 'email', header: 'Email' },
],
})
return <DataTableView table={table} loading={loading} />
}
The table state lives in React; the page shell stays Inertia-managed. No full-page reload on pagination.
Security notes
- Never call
->searchable()or->sortable()with untrusted column names. The package enforces whitelisting — only listed columns can be searched/sorted — but you must define the list yourself. - Use
->authorize()for any non-public table. Or check authorization in the controller before returning the DataTable. - The package is read-only — it does not perform writes, so SQL injection surface is limited to filter values which are bound parameters.
Configuration
The full config/laravel-tanstack.php:
return [
'default_per_page' => 25,
'max_per_page' => 100,
'case_insensitive' => true,
'report_exceptions' => true,
];
Testing
composer test
Requirements
- PHP 8.2+
- Laravel 11.x or 12.x
Roadmap
- PostgreSQL-specific
ILIKEsearch optimization - Laravel Scout integration for full-text search
- Vue 3 and Svelte FE companions
- Excel/CSV export endpoint
- Saved view / preset support
Contributing
Contributions are welcome — bug reports, feature requests, and pull requests.
Before opening a PR:
- Fork the repo and create a feature branch from
main:git checkout -b feat/short-description - Install dependencies:
composer install - Make your change. Keep the public API stable unless the PR is explicitly a breaking change.
- Add or update tests under
tests/Featureortests/Unit. New features without tests will not be merged. - Run the full quality gate locally — it must pass before you push:
composer format # Laravel Pint composer analyse # PHPStan composer test # Pest - Update
CHANGELOG.mdunder the[Unreleased]section. Use the Keep a Changelog categories:Added,Changed,Deprecated,Removed,Fixed,Security. - Open the PR against
mainwith a clear description of the change, the motivation, and any breaking-change notes.
Branch naming: feat/..., fix/..., docs/..., refactor/..., test/....
Commit style: Conventional Commits preferred (feat:, fix:, docs:, chore:, refactor:, test:).
Issues: When filing a bug, include the Laravel version, PHP version, a minimal reproduction, and the actual vs expected behavior. For feature requests, describe the use case before the proposed API.
License
MIT.
Related Packages
Complete query builder system with filters, sorting, search, and pagination. Bui...
A package to easily transform your Eloquent models into JSON to be used by Datat...