alp-develop/laravel-livewire-tables
Reactive Livewire data tables for Laravel—search, sort, filter, paginate, export, and bulk actions with zero JavaScript. Supports Laravel 10–13, Livewire 3–4, PHP 8.1+, Tailwind or Bootstrap 4/5, plus dark mode and configurable themes.
Installation (unchanged):
composer require alp-develop/laravel-livewire-tables
php artisan vendor:publish --tag=livewire-tables-config
Configure config/livewire-tables.php (required for theme, dark mode, and styling).
First Use Case (updated for security):
php artisan make:livewire Admin/UsersTable
Add the table component to your view (ensure wire:ignore for non-table elements):
<livewire:admin.users-table />
Basic Table Definition (with security best practices):
use AlpDevelop\LivewireTables\DataTableComponent;
use AlpDevelop\LivewireTables\Column\TextColumn;
use AlpDevelop\LivewireTables\Column\ViewColumn;
public function table()
{
return DataTable::of(User::query())
->addColumn('Name', TextColumn::make('name'))
->addColumn('Email', TextColumn::make('email'))
->addColumn('Actions', ViewColumn::make('actions')
->view('livewire-tables::columns.actions', [
'url' => fn($row) => route('users.edit', $row)
]));
}
Render the Table (with eager loading):
public function configure()
{
$this->setEagerLoad(['posts']); // Declare relations upfront
}
public function render()
{
return view('livewire.admin.users-table');
}
Data Source Binding (with eager loading):
DataTable::of(User::query()->where('active', true))
->setEagerLoad(['posts']); // Apply eager loading
Column Definitions (with security):
TextColumn::make('column_name')ViewColumn::make('column_name')->view('path.to.view')ViewColumn::make('actions')
->view('livewire-tables::columns.actions', ['url' => fn($row) => route('users.edit', $row)])
Reactive Features (with validation):
DataTable::of(User::query())
->search(['name', 'email']) // Search capped at 200 chars
->sort('name') // Field whitelisted
->paginate(10); // Validated against perPageOptions
Bulk Actions (with TOCTOU protection):
DataTable::of(User::query())
->addBulkAction('Delete', fn($rows) => User::whereIn('id', $rows)->delete());
// IDs are now intersected with live query results
Exporting (with sanitization):
DataTable::of(User::query())
->addExport('CSV', fn() => User::all()->toArray());
// Column labels are now escaped via escapeCsvValue()
php artisan vendor:publish --tag=livewire-tables-views
dark_mode: true) with sanitized CSS:
'colors' => [
'primary' => '#3b82f6', // Validated against CSS allowlist
],
@layer components {
.livewire-tables-table {
@apply bg-white dark:bg-gray-800;
}
}
->apiResponse() with validated perPage:
->apiResponse()->validatePerPage();
Config Required (unchanged): Forgetting to publish the config breaks theming and styling.
Query Scoping (updated for eager loading):
// Good: Use setEagerLoad() in configure()
public function configure() {
$this->setEagerLoad(['posts']); // Applied in render()
}
Column Caching (now 3-tier):
Column definitions are cached per request in cachedColumns, cachedVisibleColumns, and cachedSearchableColumns.
Bulk Action IDs (now TOCTOU-protected): Bulk actions automatically intersect with live query results—no manual filtering needed.
New Security Risks:
[a-zA-Z0-9_.] allowed in sort fields.escapeCsvValue().debug: true in config to log queries and table events.wire:ignore on non-table elements to avoid reactivity conflicts.resolveFilters() now caches filter resolution (previously called 5x per request).FilterStep and SortStep build lookup maps in constructors (O(1) instead of O(n)).Custom Columns (now XSS-safe):
class StatusColumn extends TextColumn {
public function __construct() {
parent::__construct('status');
$this->format(fn($value) => ucfirst($value)); // Escaped via TextColumn
}
}
Hooks (with Engine subclassing):
// Extend Engine for custom pipeline logic
class CustomEngine extends \AlpDevelop\LivewireTables\Engine {
// Override applySteps()
}
public function getEngine() {
return new CustomEngine($this);
}
Dark Mode (with CSS sanitization): Override dark mode styles via published assets (colors are now validated):
@layer components {
.dark .livewire-tables-table {
@apply bg-gray-900;
}
}
Localization (unchanged):
Translate labels via config/livewire-tables.php:
'labels' => [
'search' => 'Filter',
],
New: Eager Loading:
Declare relations in configure():
public function configure() {
$this->setEagerLoad(['posts', 'roles']);
}
New: Engine Subclassing:
Override getEngine() to customize pipeline orchestration:
protected function getEngine() {
return new CustomEngine($this);
}
How can I help you explore Laravel packages today?