laravel-enso/tables
Laravel Enso Tables provides a fluent backend API for building dynamic, data-driven tables in Laravel, with server-side querying, filtering, sorting, pagination, and customizable columns for Enso’s Vue interfaces.
composer.json and confirm it will even install in your app. On Laravel 10/11 you will almost certainly need a fork or to vendor the backend pieces manually.laravel-enso/enso), its permission/menu structure, and its frontend asset pipeline. If you are not building an Enso admin panel, budget time for extracting the Vue component and wiring the controller trait yourself.composer require laravel-enso/tables
php artisan vendor:publish --provider="LaravelEnso\Tables\AppServiceProvider"
Inspect the published config and the package's src/app/Services/Table and src/app/Traits directories first — they define the base class and the controller contract.// app/Tables/UsersTable.php
namespace App\Tables;
use App\Models\User;
use LaravelEnso\Tables\App\Services\Table\Table;
class UsersTable extends Table
{
protected function query()
{
return User::select(['id', 'name', 'email', 'created_at']);
}
protected function templatePath(): string
{
return app_path('Tables/Templates/users.json');
}
}
{
"columns": [
{ "label": "ID", "name": "id", "sortable": true },
{ "label": "Name", "name": "name", "sortable": true, "searchable": true },
{ "label": "Email", "name": "email", "searchable": true },
{ "label": "Created", "name": "created_at", "sortable": true }
],
"buttons": ["create", "export"],
"actions": ["edit", "destroy"]
}
Expose it through the controller trait:
use LaravelEnso\Tables\App\Traits\Table;
class UserController extends Controller
{
use Table;
protected function init()
{
return UsersTable::class;
}
}
Route::get('users/table', [UserController::class, 'index'])->name('users.table');
Then render the component in your Vue view (exact prop names vary by version — read the component's props in resources/js):
<vue-table :id="usersTable" name="users" route="users.table" />
query() returns a builder, never results. The package applies search, sort, filters, pagination, and counts itself. Returning get() or paginate() defeats the whole design and is the most common first mistake.name values must match the selected columns or explicit aliases. Keep templates in app/Tables/Templates and review them in code review like any other interface change.query(). Apply a policy-driven scope (->visibleTo(auth()->user()) or equivalent) on the base builder, and still protect the route with middleware. Hiding buttons is not authorization.filters() returning filter objects (date, interval, select, search) and mirror the same keys in the template's filters block so the UI and backend agree.actions() to control which row buttons render and the permission/route each requires. Keep destructive or bulk operations behind Enso permissions or explicit Gate checks.meta() and extra() for aggregates. Use meta() for footer totals and extra() for side payloads like dropdown options. They run on every request, so keep them cheap and cached where possible.query() / templatePath() rather than duplicating JSON.start, length, search, sort, filters) and assert both data and meta. Snapshot the template JSON to catch accidental contract changes.name that does not match a selected column or alias renders an empty cell with no error. Alias computed columns explicitly and mark them "sortable": false unless you also handle sorting.user.name-style columns require joins plus careful searchable/sortable handling. Watch for N+1 queries and duplicate rows from one-to-many joins.php artisan config:clear / cache:clear, and rebuild frontend assets whenever JSON or column renderers change.select *; on very large tables consider a capped or estimated total.created_at sorting can shift across timezones or DST boundaries. Standardise on the app timezone and test around midnight.How can I help you explore Laravel packages today?