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.
Contents:
The package uses a pipeline pattern to build and execute table queries:
DataTableComponent
└─ Engine
├─ SearchStep → applies LIKE search across searchable columns
├─ FilterStep → applies each active filter value
└─ SortStep → applies ORDER BY
The Engine is created once per Livewire request and cached on the component. render(), getSelectedIds(), and exportCsvAuto() all reuse the same Engine instance.
Engine::process() applies all registered steps and then paginates. Engine::applySteps() applies steps without paginating — used by export and bulk selection to avoid a redundant COUNT(*).
// Internal usage
$engine = (new Engine($columns, $filters))
->addStep(new SearchStep($columns))
->addStep(new FilterStep($filters))
->addStep(new SortStep($columns));
// With pagination
$paginator = $engine->process($query, $state);
// Without pagination (export, bulk)
$builder = $engine->applySteps($query, $state);
Implement StepContract and register it in configure():
use Illuminate\Database\Eloquent\Builder;
use Livewire\Tables\Core\Contracts\StateContract;
use Livewire\Tables\Core\Contracts\StepContract;
class SoftDeleteStep implements StepContract
{
public function apply(Builder $query, StateContract $state): Builder
{
return $query->withTrashed();
}
}
Register it by adding it in configure() — or by overriding getEngine() for full pipeline control:
// Option 1 (recommended): Override configure() — no pipeline re-definition needed
// This is for per-component query scoping; use custom steps for generic behavior.
// Option 2: Override getEngine() for complete pipeline customisation
class MyTable extends DataTableComponent
{
protected function getEngine(): Engine
{
if ($this->cachedEngine !== null) {
return $this->cachedEngine;
}
$columns = $this->resolveColumns();
$filters = $this->resolveFilters();
$this->cachedEngine = (new Engine($columns, $filters))
->addStep(new SearchStep($columns))
->addStep(new FilterStep($filters))
->addStep(new SortStep($columns))
->addStep(new SoftDeleteStep);
return $this->cachedEngine;
}
}
Note:
Engineis a concrete class (notfinal) andgetEngine()isprotected. Override either to customise the pipeline. Always assign to$this->cachedEnginebefore returning so thatrender(),getSelectedIds(), and export reuse the same instance.
State is a value object passed to each step:
new State(
search: string,
sortFields: array, // ['field' => 'asc'|'desc', ...]
filters: array, // ['filter_key' => $value, ...]
perPage: int,
page: int,
)
Steps receive StateContract — they can read search, filters, and sort state but cannot modify it.
| Contract | Description |
|---|---|
EngineContract |
process(), applySteps(), addStep(), columns(), filters() |
StepContract |
apply(Builder, StateContract): Builder |
FilterContract |
run(Builder, mixed): Builder, normalizeValue(mixed): mixed |
ColumnContract |
field(), getLabel(), isVisible(), isSortable(), isSearchable() |
StateContract |
search(), sortFields(), filters(), perPage(), page() |
DataTableComponent uses 11 traits. Each trait has [@requires](https://github.com/requires) docblocks documenting its dependencies:
| Trait | Responsibilities |
|---|---|
HasColumns |
columns() resolution, column caching, toggle hidden |
HasFilters |
filters() resolution, filter state management |
HasSearch |
Search term with length clamping |
HasSorting |
Sort field/direction state |
HasPerPage |
Per-page with whitelist validation |
HasBulkActions |
Selected IDs, excluded IDs, bulk action dispatch |
HasExport |
CSV streaming export with formula injection prevention |
HasStateCache |
Session persistence and dirty tracking |
HasListeners |
Event-based refresh |
HasPagination |
Pagination view resolution |
HasConfiguration |
configure() hook for per-request setup |
How can I help you explore Laravel packages today?