fsi/datagrid-bundle
FSI DataGrid Bundle is a Symfony package for building flexible, configurable data grids. It supports columns, filters, sorting, pagination, actions, and exports, with Doctrine and Twig integration for quickly rendering admin-ready tables.
Read this first: the package is a Symfony bundle, not a Laravel package. It contains almost no grid logic itself — its description says it plainly: it registers the FSi DataGrid component's service and the Twig rendering functions. Everything interesting lives in the underlying fsi/datagrid component (DataGrid, Column\*, DataSource\*). There is no service provider, no facade, no config file that Laravel's package discovery would pick up.
So the decision fork is:
fsi/datagrid-bundle and use the fsi_datagrid.factory service plus the Twig helpers.fsi/datagrid, construct the factory yourself in a service provider, and write your own Blade renderer. That is maybe 60–100 lines of glue and avoids dragging Symfony DI into the app.Minimal Symfony path:
composer require fsi/datagrid-bundle
// config/bundles.php (Symfony Flex often won't auto-register this one)
FSi\Bundle\DataGridBundle\FSiDataGridBundle::class => ['all' => true],
// Controller — autowire the factory (service id: fsi_datagrid.factory)
public function list(DataGridFactory $factory, UserRepository $users)
{
$grid = $factory->createDataGrid('users');
$grid->addColumn('id', 'number', ['sortable' => true]);
$grid->addColumn('email', 'text', ['sortable' => true]);
$grid->addColumn('createdAt', 'datetime', ['label' => 'Joined']);
$grid->setData($users->createQueryBuilder('u'));
return $this->render('user/list.html.twig', ['grid' => $grid]);
}
{{ datagrid_render(grid) }}
{# the bundle also ships a JS helper for ajax sorting/pagination — check Twig/ for the exact function names #}
Laravel equivalent skeleton:
// app/Providers/DataGridServiceProvider.php
$this->app->singleton(DataGridFactory::class, fn () => new DataGridFactory());
$grid = app(DataGridFactory::class)->createDataGrid('users');
$grid->addColumn('email', 'text', ['sortable' => true]);
// ... render with @include('partials.datagrid', ['grid' => $grid])
First use case to prove it out: a paginated, sortable table over one Doctrine entity, rendered server-side. If that works, the rest is column types and a custom data source.
Where to look first, in order: the fsi/datagrid README (that's where the real docs are), src/Resources/config/*.xml for the registered service ids, DependencyInjection/ for the extension, and Twig/ for the extension class that defines the actual function names. The bundle README is thin.
Build grids in a dedicated factory, not in controllers. One class per grid (UserGrid, OrderGrid) that takes the DataGridFactory and returns a configured grid. This is the single biggest maintainability win and makes the grids unit-testable without HTTP.
Columns are typed and configured by array options. The component ships Text, Number, Date/DateTime, Boolean, and Actions column types. Options cover label, sortable, translation_domain, and value formatting. Register custom types by extending the column base class and teaching the factory about it.
Data sources are the integration seam. In Symfony you'll use the Doctrine ORM data source, which accepts a query builder. For Laravel you implement DataSourceInterface yourself over an Eloquent builder — this is the cleanest bridge and keeps the component ignorant of Eloquent:
final class EloquentDataSource implements DataSourceInterface
{
public function __construct(private Builder $query) {}
public function getData(): iterable { return $this->query->get(); }
public function getTotalCount(): int { return $this->query->count(); }
}
Use the array/in-memory source for tests and for small static tables (enum lookups, config lists) where hitting the DB is pointless.
Pagination and sorting are your responsibility. The component gives you the hooks (setData, limit/offset, sort column + direction); it does not parse ?page= or ?sort= for you. In Laravel, read from the request in the controller, apply orderBy/skip/take to the builder before handing it to the data source, and pass the resolved page/limit into the grid. Keep it in one place — a small GridRequest value object that maps query params to (page, limit, sortColumn, direction) pays for itself immediately.
Actions columns take closures/URLs. For row actions ("edit", "delete"), use the actions column with a route/URL generator so links are built per row rather than concatenating strings in a template.
Rendering: the Twig helper is a thin wrapper around getColumns() / getData(). In Blade, write a single partial that iterates columns for the header and rows for the body, plus a pagination partial. It's boring, deliberately so, and it's the piece you'd be reimplementing anyway.
AJAX/JS path: the bundle can emit the JavaScript needed for client-driven sorting/pagination. If you go this route, expose a JSON endpoint that rebuilds the grid from request params and returns the rendered rows — don't try to compute the grid twice in different places.
Test the grid, not the page. Because the grid is a plain object, you can assert "column email is sortable and renders the right value" in a unit test with an array data source. This is the main reason to use a component like this instead of hand-rolled Blade tables.
fsi/datagrid directly. The bundle's only real value is DI registration and Twig functions, both of which you'd replace.Twig/ and confirm. Don't build a template around a guessed name.getTotalCount() is wrong, the pager silently breaks — always verify it against the unpaginated query in a test.?sort= to a column name must reject unknown columns, or you get SQL errors (or worse) from arbitrary column injection into orderBy.DataGrid/Column classes are framework-agnostic — that's why the Eloquent bridge works. Don't assume the whole package is Doctrine-bound.DataSourceInterface implementations, custom column types, and (in Symfony only) overriding the bundle's Twig templates under templates/bundles/FSiDataGridBundle/. In Laravel the extension point is simply your own renderer.How can I help you explore Laravel packages today?