developerawam/livewire-datatable
A powerful, flexible DataTable component for Laravel Livewire that turns your Eloquent models into interactive tables with zero configuration. Server-side rendering, search, sorting, filters, pagination, exports, and full customization.
Install and wire up in four steps.
composer require developerawam/livewire-datatable
php artisan vendor:publish --tag="livewire-datatable-config" # optional but do it on day one
Then make the package's Blade views visible to your CSS pipeline. This is the single step people forget, and the symptom is a fully functional table that looks like 1998:
// tailwind.config.js (Tailwind v3)
content: [
"./resources/**/*.blade.php",
"./vendor/developerawam/livewire-datatable/resources/views/*.blade.php",
],
/* Tailwind v4: resources/css/app.css */
@import "tailwindcss";
@source '../../vendor/developerawam/livewire-datatable/resources/views/*.blade.php';
Bootstrap 5 users skip this — the package auto-detects the framework. You can force it with DATATABLE_TEMPLATE=bootstrap|tailwind in .env.
First use case: point it at a model and walk away.
The idiomatic pattern is a thin Livewire wrapper that supplies config, and the package component nested inside its view:
// app/Livewire/UsersTable.php
public function render()
{
return view('livewire.users-table', [
'model' => User::class,
'columns' => [
'id' => 'ID',
'name' => 'Name',
'email' => 'Email',
'department.name'=> 'Department',
'created_at' => 'Joined',
],
'searchable' => ['name', 'email'],
]);
}
{{-- resources/views/livewire/users-table.blade.php --}}
<div>
<livewire:livewire-datatable
:model="$model"
:columns="$columns"
:searchable="$searchable" />
</div>
<livewire:users-table />
Where to look first: config/livewire-datatable.php after publishing. Nearly every styling, pagination, and template decision lives there, and the README's per-component options all mirror config keys. Read the config file once and you've read most of the docs.
Reality check on requirements before you commit: PHP ^8.2, Laravel ^12 or ^13, Livewire ^4. That's a narrow window — if you're on Laravel 11 / Livewire 3 this package is not installable, and 64 stars with zero dependents means you are an early adopter, not a follower. Budget for reading the source when something is ambiguous.
Formatters are the workhorse. Declare intent in config instead of writing Blade partials:
'formatters' => [
'created_at' => 'datetime',
'balance' => ['type' => 'money', 'options' => ['symbol' => '$', 'decimals' => 2]],
'is_active' => 'boolean',
'bio' => ['type' => 'words', 'options' => ['words' => 12, 'end' => '…']],
'name' => ['type' => 'link', 'options' => [
'route' => 'users.show',
'params' => ['user' => 'id'], // assoc form when param name ≠ column name
'class' => 'text-blue-600 hover:underline',
]],
],
The link formatter also accepts a static url with {column} interpolation (/users/{id}/edit), which is the cheapest way to add row-level actions without a custom template.
Relationships: declare both sides. Add the relation to $with on the model, then use dot notation in columns, searchable, and formatters. Sorting on department.name is handled for you — but only because the package resolves the join. Keep those relation columns out of unsortable intentionally; adding unsortable there silently removes a feature users expect.
Scopes for authorization-shaped filtering, not for per-request filters. 'scope' => 'active' is great for a fixed dashboard. For anything user-driven (tenant, status dropdown, date range), keep it in the query the wrapper builds or use the package's own filter/date-range API — otherwise you end up re-rendering the component with a new scope string on every keystroke, which defeats the session persistence.
Sensible wrapper split for real apps:
active, tenantOwned) → scope / model global scopes.Exports: treat column selection as a feature, not a checkbox. The custom export path lets the user pick columns, and link-formatted columns deliberately export their raw value (no URL). Design the export column list to match what finance/ops actually paste into a spreadsheet — that usually means raw dates and unformatted numbers, which the formatter-aware export already gives you.
Theme once, override rarely. Set CSS classes, dark mode, and per-page options globally in config; use per-component overrides only for genuinely different tables (e.g. a compact audit log vs. a spacious customer list). Mixed inline class strings across components is how these codebases rot.
Schema cache will bite you after migrations. Introspection is cached (DATATABLE_SCHEMA_CACHE_TTL, default 3600s). Add a column, and your table silently doesn't show it for an hour. Wire cache clearing into your deploy/migration flow — the README points at livewire-datatable:schema:* and DataTable::clearSchemaCache(). In local dev, set DATATABLE_SCHEMA_CACHE_TTL=0 and stop thinking about it.
Session-persisted search surprises people. Global search and advanced filters persist per table in the Laravel session (DATATABLE_SESSION). This is usually desirable, but in tests and demos it produces "why is this table pre-filtered?" moments. Know the toggle: false disables persistence and the filter panel close resets state.
Unstyled table = the Tailwind content glob. Almost always the @source / content entry missing, or a cached CSS build. Check that before assuming the template resolution is broken.
Searchable columns must be declared explicitly. The columns array drives display; searchable drives the WHERE … LIKE clauses. A column visible but not searchable is a support ticket waiting to happen. Same for relation columns — search on department.name only works if it's listed.
Export is your first memory risk. Server-side pagination protects the browse experience but not the export path — a 100k-row Excel export is a different beast. Cap it, queue it, or scope it. Also verify which spreadsheet/PDF driver the package expects in composer.json (maatwebsite/excel, a PDF renderer) since the README documents options but doesn't enumerate driver dependencies — don't promise a PDF export in a sprint before running one locally.
Livewire 4 + nested components: watch the render budget. The wrapper → package component nesting is clean, but it means two component lifecycles per table. If you have a dashboard with five tables, prefer separate pages or lazy-load, and pass only scalars/class-strings between them (as the README does) rather than collections.
no column numbering is pagination-aware — good, but it means you shouldn't hand-roll a row index; you'll get off-by-page-number bugs. Use the built-in.
Extension points, in order of safety: formatters → custom cell templates (Blade components) → published views → forking. Everything above "custom templates" is a maintenance liability against a 64-star package with no dependents. If you find yourself patching vendor files, that's the signal to write your own table.
Debugging checklist when a table misbehaves:
php artisan view:clear && npm run build — stale views/CSS cause most "broken" reports.->toSql() / DB::listen on the underlying query — confirm the relation join and search clauses look sane.unsortable — a sort click that does nothing is usually a column you excluded.DATATABLE_SCHEMA_CACHE_TTL=0 and DATATABLE_SESSION=false to isolate persistence bugs.Index for the queries you're enabling. Sorting and searching on email, created_at, and relation foreign keys without indexes turns "handles thousands of records efficiently" into a 3-second page load at 50k rows. The package optimizes the transport, not your schema.
How can I help you explore Laravel packages today?