bricknpc/eloquent-tables
Eloquent Tables is a Laravel package for turning Eloquent models into ready-to-use tables. Define columns, sorting, searching, and pagination declaratively, then render clean HTML tables or feed structured data to your frontend with minimal boilerplate.
eloquent-tables is a table-building layer for Eloquent models — you describe columns, filters, sorting and pagination declaratively and it renders the result set, rather than hand-rolling <table> markup and a controller query for every index page.
Minimal path:
composer require bricknpc/eloquent-tables and confirm the service provider is auto-discovered.php artisan vendor:publish --tag=eloquent-tables-config) and read it top to bottom — defaults for per-page counts, view paths and query behaviour live there, and it's the fastest way to learn what the package considers configurable.main happens to be.Post) with pagination and replace it with a table definition over Post::query(). Keep the old Blade view around until parity is reached — columns, sortable headers, empty state, pagination links.Start with a single table and no filters. Filters, actions and exports are the parts most likely to need per-project design decisions, so defer them until the base table renders correctly against real data.
Definition class per table. Put each table in its own class (or a dedicated method on a Livewire component) rather than inlining the definition in a controller. Tables accrete columns and filters quickly, and a named class is what makes them testable and reusable across an index page and an export endpoint.
Feed it a query, not a model. Pass a constrained Builder (Post::query()->where('team_id', $team->id)) so ownership/tenant scoping is explicit at the call site. This keeps authorization visible in your own code instead of buried in package config, and makes the same table definition reusable under different scopes.
Eager-load for the columns you show. Any column rendering a relation (author.name) will N+1 unless the base query with()s it. Watch the query count on the first render — this is the single most common performance regression when adopting a table package.
Sorting and filtering through query state, not request parsing. Let the package own the request → query translation, and keep your own code out of $request->input('sort'). If you need a custom filter, implement it against the builder the package hands you so it composes with the built-in ones and with pagination.
Authorize at the boundary. Table actions (edit, delete, bulk operations) bypass your resource controllers. Either gate them per-action in the definition or ensure the underlying query can only return rows the user may act on — do not rely on the UI hiding buttons.
Exports reuse the definition. The payoff of a named table class is that an export job can build the same table, drop pagination, and stream to CSV. Resist writing a parallel select() for exports; drift between screen columns and export columns is a recurring bug.
Livewire/Blade integration. Bind the table to component state ($this->table) so sort/filter/page changes are round-trips rather than full reloads. Keep the table's public state minimal and serialisable — closures in filter definitions are the usual culprit when a component fails to hydrate.
distinct(). Tables that join for a filter column and also use distinct() can produce confusing page counts. Verify counts against a raw query before blaming the package.orderByRaw — and confirm with ->toSql() plus DB::listen().DB::listen() around a single render tells you more than the rendered HTML: it exposes both missing eager loads and filters that were silently dropped.data- attributes) change between minor versions, and brittle selector assertions make upgrades painful.php artisan config:clear in local dev — stale cached config is a frequent source of "my per-page default isn't applied".How can I help you explore Laravel packages today?