aginev/datagrid
aginev/datagrid is a Laravel package for quickly building interactive data grids from Eloquent models. It supports column definitions, sorting, filtering, pagination, custom rendering, and row actions, making admin tables easy to add with minimal code.
Start by reading the repo's README end-to-end, then open src/ and the config/ file before writing any code — this package predates many Laravel conventions you may assume, so the source is the ground truth.
Install and wire up:
composer require aginev/datagrid
Aginev\Datagrid\DatagridServiceProvider in config/app.php and add the facade alias.config, views, JS/CSS assets) with php artisan vendor:publish --provider="Aginev\Datagrid\DatagridServiceProvider". Check the README for the exact tag names.composer.json constraints (PHP version, illuminate/* versions) match your app. If you're on Laravel 8+, expect to test this on a branch first.First use case — a users table. The lowest-friction first step is a read-only grid over one Eloquent query:
User::query() and declare a handful of columns (id, name, email, created_at).Where to look first:
README.md examples — copy one verbatim, then modify.src/ for the base grid/column classes you'll extend or configure.resources/views/ and resources/assets/ (if present) to understand the frontend contract — column names in your grid must match what the JS renders.Once a single grid renders correctly, the rest of the package's surface area (filters, custom cell rendering, exports) is incremental.
One grid class per resource. Rather than configuring grids inline in controllers, define a small class per list view (e.g. App\Grids\UsersGrid) that encapsulates the query, columns, allowed sorts, and filters. This keeps controllers thin, makes the grid testable, and gives you a single place to change column behavior. If the package ships a base grid class, extend it; otherwise wrap it in a thin service class.
Query-first, columns-second. Build the grid from an Eloquent/query-builder instance and select() only the columns you display. This is the single biggest performance lever: it prevents SELECT * on wide tables and keeps the package from hydrating fields it will never render. For relationship columns, use explicit joins with aliased columns rather than lazy-loading.
Server-side AJAX endpoint. For anything beyond a few hundred rows, use the package's server-side mode: one GET route returns JSON (rows, total count, filtered count), and the frontend handles paging/sorting. Keep the route behind the same auth middleware as the page that renders it — a JSON endpoint is easy to forget to protect.
Composition over inheritance for columns. Use closures or a column-renderer callback for anything non-trivial (status badges, action links, formatted dates, truncation). Reserve subclassing the grid for cross-cutting behavior (tenant scoping, permission-based column visibility, audit columns).
Reusable base grid for tenancy/permissions. A common day-to-day pattern: an App\Grids\BaseGrid that applies a global scope (e.g. where('tenant_id', ...)) and filters columns by the current user's permissions. Every concrete grid extends it, so new list views are automatically secured.
Testing. Assert the JSON shape rather than rendered HTML where possible: hit the grid endpoint as an authenticated user, assert total, data count, and that a sort parameter reorders rows. This is far more stable than snapshotting Blade output.
Integration with other features. Export buttons typically re-use the same query/grid definition — factor the query construction into a method so the export controller and the grid controller share it. Same for any "bulk action" toolbar.
Version drift is the headline risk. Last release was February 2021 and the description targets Laravel 5. Expect framework API changes, facade alias deprecations, and possible incompatibility with Laravel 9/10+ (especially around Str/Arr helpers and type hints). Pin the package, run the full test suite, and be prepared to vendor/fork it if you need modern Laravel. Treat it as "unmaintained but functional" rather than "supported."
Check the JS/CSS contract. The PHP side and the frontend assets version are coupled. If you upgrade the package, re-publish its assets — stale assets in public/ are the most common cause of "it renders but sorting/paging does nothing." Clear view and config caches after any config change (php artisan optimize:clear or config:clear + view:clear).
N+1 and count queries. Grids are notorious for hidden N+1s on relationship columns and an extra COUNT(*) per request. Watch Laravel Debugbar / Telescope on the grid endpoint. Use with() for displayed relations or joins, and check whether the package's server-side mode recomputes an unfiltered count you can cheaply cache.
Column name mismatches fail silently. A grid column key that doesn't match the rendered JS column definition usually produces a blank cell rather than an error. When a column is empty, first compare your PHP column keys against the JS columns array (or the package's view), then check the DB alias.
Sorting/filtering allowlists. Never pass raw request parameters into orderBy() or where(). Verify the package whitelists sortable columns; if it doesn't, add an allowlist yourself in the grid definition. This is the classic injection surface for datagrid packages.
CSRF and auth on AJAX calls. If the grid fetches over AJAX, ensure the JS sends the CSRF token and the endpoint is inside your auth middleware. A 419/401 here looks like "empty grid" with no useful console error — check the network tab before debugging the PHP.
Debugging pattern. When a grid misbehaves, temporarily switch to non-server-side (small dataset) rendering to isolate whether the problem is the query/columns or the JS. Dump the query with ->toSql()/->getBindings() or dd() the grid's data payload before it's serialized.
Extension points. The reliable customization levers are (a) column renderers/closures, (b) overriding the grid's row/data transformation method, and (c) overriding the published views. Prefer (a) and (b); copying views couples you to the package's markup. Because the package is lightly maintained, keep your customizations in your own classes/wrappers so a future fork or replacement is a small diff.
Have an exit plan. Given 47 stars and a 2021 last release, wrap the package behind your own BaseGrid interface so you can swap in a maintained alternative (or hand-rolled query + Blade partial) without touching every controller.
How can I help you explore Laravel packages today?