prezent/grid
A Laravel package for building data grids and tables in PHP. Define columns and rows, control formatting and rendering, and display structured, searchable data in your Blade views with minimal configuration and reusable, customizable grid classes.
prezent/grid is a framework-agnostic PHP library, not a Laravel package. There is no service provider, no config file, and no Blade views shipped. You wire it up yourself.
composer require prezent/grid
Register the factory once in AppServiceProvider:
use Prezent\Grid\GridFactory;
use Prezent\Grid\Extension\Core\CoreExtension;
$this->app->singleton(GridFactory::class, function () {
$factory = new GridFactory();
$factory->addExtension(new CoreExtension());
return $factory;
});
Your first grid — an admin index table backed by an array:
use Prezent\Grid\GridFactory;
use Prezent\Grid\DataSource\ArrayDataSource;
public function index(GridFactory $factory)
{
$grid = $factory->createGrid('users', function ($builder) {
$builder->addColumn('id', null, ['property_path' => 'id']);
$builder->addColumn('name', null, ['property_path' => 'name']);
$builder->addColumn('email', null, ['property_path' => 'email']);
});
$view = $grid->createView(new ArrayDataSource(
User::query()->orderBy('id')->get()->toArray()
));
return view('users.index', ['grid' => $view]);
}
Look first at GridFactory, GridBuilder, the DataSource implementations (ArrayDataSource is the one you'll use with Eloquent), and Extension/Core for the built-in column type. Your first real use case is almost always "replace a hand-written <table> in an admin screen with a declarative column list."
Define grids as classes, not closures. Subclass the library's grid type (or wrap the closure in a dedicated App\Support\Grids\UserGrid class) so the column definition is testable and reusable across controllers and exports.
Map Eloquent to arrays at the boundary. The data source consumes plain rows; call ->get()->toArray() or ->map(fn ($u) => [...]) before handing data over. This keeps lazy-loading surprises and N+1 queries inside your query layer where you can with() them, rather than inside cell rendering.
Use property_path for anything nested. The core column type delegates to symfony/property-access, so property_path => 'address.city' works for both arrays and objects. Use a closure/callable column when the value needs computation — don't compute in Blade.
Own sorting and pagination yourself. The grid renders what you give it. Read ?sort= and ?page= in the controller, apply them to the query builder, paginate, then feed $paginator->items() to the data source and pass the paginator to the view separately.
Keep actions as data. For an "Actions" column, build URLs in the controller (or a small presenter) and expose them as row keys, e.g. ['edit_url' => route('users.edit', $u)], then render <a> tags in your Blade partial. This avoids embedding routing into grid definitions.
One Blade partial per grid. Iterate $grid->getColumns() for the header and $grid->getRows() for the body. Because there's no template layer, this partial is your integration point — keep it dumb and escaped.
Compose with custom types and extensions. If you use the same status badge or date format in several grids, register a custom column type or a grid type extension on the factory in the service provider. That's the intended extension seam.
Test with ArrayDataSource. Grid definitions are cheap to unit test: build the grid, pass a fixed array, assert on column names and rendered row values. No database needed.
composer.json, and consider whether a small in-house table builder would be less risk than an unmaintained dependency for what is essentially column metadata plus a render loop.{{ }} in Blade; never {!! !!} for raw column values.property_path typically yields null cells rather than an exception. When a column is blank, dump the first row's keys before suspecting the library.register().property_path quirks. Missing paths and null intermediate objects behave per Symfony PropertyAccess — it returns null rather than throwing in the default configuration, which masks bad data. Assert on expected columns in tests.dump($grid->getColumns()) and dump(iterator_to_array($view->getRows())) early; most issues are the wrong data shape (Eloquent models vs. arrays) reaching the data source.How can I help you explore Laravel packages today?