Data tables · Open source
Laravel Grid.js
Turn any Eloquent query into a searchable, sortable, paginated Grid.js data table with one fluent PHP call.
What it solves
A data table over a large Eloquent query needs search, sort and pagination on the server, plus a data endpoint that must not let the browser choose which model or columns to read.
Writing that route, controller and JavaScript for every table is repetitive and easy to get wrong.
You build the grid from your own query in PHP and echo it in Blade. Grid.js fetches one page at a time from a built-in endpoint that only runs the query and columns sealed in an encrypted token, with no route, controller or JavaScript to write.
- Keeps your query: where clauses, scopes, joins, eager loads and default order
- Search (LIKE, relation columns included), sort and LIMIT/OFFSET pagination run in the database
- Encrypted grid token bound to the signed-in user or guest session and valid for 720 minutes; the endpoint never takes a model or column name from the browser
- Built-in date, datetime, number, currency, boolean and link formats, all escaped
- Action buttons with {id} URL placeholders; link formatters refuse javascript: URLs
- Grid.js 6.2.0 pinned on jsDelivr with SRI, or served from your public/ folder
Install
Requires PHP 8.3+ and Laravel 12 and 13.
composer require amjadiqbal/laravel-gridjs
Usage
use AmjadIqbal\GridJS\Facades\Grid;
$customers = Grid::fromQuery(Customer::active()->with('team'))
->columns(['name', 'email', ['field' => 'team.name', 'name' => 'Team']])
->searchable()
->sortable()
->pagination(10);
return view('customers.index', ['customers' => $customers]);
// In the view: {{ $customers }}
Setup
The service provider, the Grid facade and the data route register themselves. Optionally publish the config and choose CDN or local assets, or copy Grid.js into public/vendor/gridjs and set GRIDJS_CDN=false.
php artisan gridjs:install
php artisan gridjs:publish-assets
Columns
Per column: field, name, width, hidden, searchable, sortable, format and formatter. Accessors and casts work in cells but cannot be searched or sorted in SQL, so switch those off for them. Relation columns are searchable through whereHas but not sortable. A column marked hidden never leaves the server: its values are left out of the JSON, and it cannot be searched or sorted. The one exception is the key field of an actions() column, which is sent because the action links need it.
->columns([
'name',
'team.name',
['field' => 'email', 'name' => 'E-mail', 'width' => '240px'],
['field' => 'full_name', 'searchable' => false, 'sortable' => false],
['field' => 'mrr', 'name' => 'MRR', 'format' => ['type' => 'currency', 'currency' => 'USD']],
])
Search, sort, pagination and actions
The browser can only pick a page, a keyword and one of the sortable fields. A grid without pagination() returns at most gridjs.max_rows rows (1000). {id} in an action URL is filled from the key field.
->searchable(['name', 'email'], 200) // only these, 200 ms debounce
->sortable(['name', 'created_at'])
->pagination(25)
->actions([['label' => 'Edit', 'url' => '/customers/{id}/edit']])
Protecting the data
Every grid fetches from GET /gridjs/data. Add auth (or any middleware) in config/gridjs.php to keep it behind login. Each grid's token is encrypted with your APP_KEY and only works for whoever the page was rendered for: the signed-in user, or for guests a random value kept in their session. A token copied to another account or browser, a tampered token, or one older than token_lifetime minutes (720 by default) gets a 403, and an expired grid needs a page reload. Binding needs the session (the web middleware); set bind_tokens to false for a stateless setup. Rotating APP_KEY also invalidates open grids. A grid rendered with no signed-in user and no session (for example on a route without the web middleware) is not given a token at all: rendering it throws an exception telling you to add the web middleware. A token without a binding gets a 403 unless you set allow_unbound_tokens to true (false by default), which issues unbound tokens there instead; they still expire.
// config/gridjs.php
'middleware' => ['web', 'auth'],
'bind_tokens' => true,
'allow_unbound_tokens' => false,
'token_lifetime' => 720, // minutes
Assets and styling
- The first grid on a page prints the Grid.js script and theme plus a small mount script, once per request.
- Add @gridjsAssets to your layout's <head> to print them there, or set gridjs.assets.inject to false and load Grid.js yourself.
- With Vite::useCspNonce() the inline scripts get the nonce.
- ->theme('mermaid') is the default; ->theme(null) and ->className([...]) let you style it with Tailwind or your own CSS.
- Custom JavaScript formatters run as-is, so never build them from user input.
Hire me
Need this built into your app, or something custom?
I wrote Laravel Grid.js. I can wire it into your Laravel app, extend it for your case, or build the feature around it. Tell me what you need and you'll get a plan and a quote, free.
Support
Found a bug or want a feature? Open an issue on GitHub. For help adding it to your own project, use the quote form above.
More Laravel packages
All packages
Laravel Driver.js
Build Driver.js product tours in PHP and render them with one Blade component.
Laravel Sonner
Sonner toast notifications for Laravel, sent from PHP and shown without React.
Laravel Ninja Keys
A Cmd+K / Ctrl+K command palette for Laravel, with the actions defined in PHP.
Laravel SweetAlert2
SweetAlert2 popups, toasts and confirm dialogs from Laravel controllers, Livewire and Alpine.
Laralink
Work on a Composer package inside the Laravel app that uses it, then switch back to the Packagist release.
Refresher
A drop-in replacement for Laravel's RefreshDatabase that restores a cached snapshot of your test database instead of running migrate:fresh.
Kiln
Artisan commands and an optional signed HTTP route to clear, warm and inspect OPcache when you deploy.
Laravel Media Viewer
Show private PDFs, video and audio from any Laravel disk without making the files public.
Laravel Swiper
Swiper sliders, carousels and image galleries for Laravel as Blade components.
Laravel MySQL Advisor
Read-only health checks for the MySQL or MariaDB database behind your Laravel app, with the SQL and the Laravel migration to fix a problem wherever a fix applies.
Laravel Webhook Console
A dashboard and replay tool for the webhooks spatie/laravel-webhook-client stores in your Laravel app.
Laravel Pennant Dashboard
A browser dashboard for laravel/pennant feature flags: per-scope toggles, sticky percentage rollouts, purge and an audit log.
Laravel Notification Inbox
Catches every non-mail Laravel notification in local and staging instead of sending it, and shows each one as it would look on the other end.
Laravel WebPush Kit
The browser side of Laravel web push: a subscribe button with a two-step permission prompt, a service worker, and routes that save each user's subscription. Tested in Chrome so far.
Laravel Redis Advisor
Read-only health checks for the Redis or Valkey behind your Laravel cache, queues, sessions and Horizon, with the redis.conf line or Laravel config that fixes each problem.