Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Livewire Datatables Laravel Package

nodus-it/livewire-datatables

Build interactive, reactive data tables in Laravel with Livewire. Add sorting, searching, filtering, and pagination using simple PHP components—no custom JavaScript required. Clean Blade templates make every table easy to customize.

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm the stack before you install. This package is Laravel + Livewire + Bootstrap 4. If your layout is Tailwind or Bootstrap 5, you are signing up to re-style the published Blade views or accept visual inconsistency — check resources/views in the vendor directory first. Also verify the Livewire constraint in composer.json of the package: 0.10.x was built against Livewire 2; Livewire 3 changed lifecycle hooks and component registration in ways that break table packages. Do not assume compatibility — read the constraint and the CI matrix.

composer require nodus-it/livewire-datatables:0.10.3   # exact pin, not ^0.10
php artisan vendor:publish --provider="Nodus\LivewireDatatables\LivewireDatatablesServiceProvider"

Publish config and views even if you plan no changes — having the views in your repo is what makes restyling and debugging possible, and it gives you a diff target when you upgrade.

Where to look first, in this order:

  1. vendor/nodus-it/livewire-datatables/README.md — this is the documentation. There is no docs site, and the GitHub release notes are a bare compare link.
  2. vendor/nodus-it/livewire-datatables/src/ — find the base table component class and the Column class. These are your real API surface. Read the method signatures rather than guessing from the README.
  3. vendor/nodus-it/livewire-datatables/resources/views/ — tells you exactly what markup and CSS classes you are inheriting.
  4. tests/ — the only executable specification of behaviour, especially sorting and search semantics.

First use case: one read-only screen. Pick an audit log, a user list, or an orders index — something with server-side pagination needs, no write actions, and no authorization subtleties. The shape you are building toward:

// app/Livewire/Tables/AuditLogTable.php
class AuditLogTable extends DataTableComponent
{
    public function getColumns(): array
    {
        return [
            Column::make('id', 'ID')->sortable(),
            Column::make('event', 'Event')->searchable(),
            Column::make('user.name', 'User')->searchable(),   // eager-load it
            Column::make('created_at', 'When')->sortable(),
        ];
    }

    public function getBuilder(): Builder
    {
        return AuditLog::query()->with('user');   // no relation without with()
    }
}

Confirm the exact base class name and abstract method signatures against src/ — that is a two-minute check that saves an hour of blind guessing.

Do not roll this out to write-action screens until the pilot renders correctly at realistic row counts and you have profiled the query log.

Implementation Patterns

One table class per screen, and a base class above them all. Create an abstract AppTable extends DataTableComponent that sets your house defaults — page size, default sort, empty-state copy, date formatting helper. Every screen table extends that, not the package class. This is the single highest-leverage decision in the whole adoption: when a minor bump breaks something, you edit one file instead of twenty.

abstract class AppTable extends DataTableComponent
{
    public int $perPage = 25;
    public function getPerPageOptions(): array { return [10, 25, 50, 100]; }
}

Extract the query into its own method, and let exports reuse it. The moment you add CSV export, scheduled digests, or a dashboard count, you will need the same filtered query outside the Livewire lifecycle. Put it in a named method (or a dedicated query object) and have both the table and the export call it:

public static function baseQuery(User $actor): Builder
{
    return AuditLog::query()
        ->where('tenant_id', $actor->tenant_id)   // scoping lives HERE
        ->with('user');
}

public function getBuilder(): Builder { return static::baseQuery(auth()->user()); }

Scope authorization in the query, never in the renderer. If a row appears in the data source, it is exposed — hiding the edit button in Blade does not hide the row. And for row actions specifically, re-check Gate::authorize() inside the action method itself; a user can call a Livewire action directly without the button ever rendering.

Custom cells: Blade partial over a closure. Closures do not survive Livewire hydration inside public properties, and they make the table class unreadable past four columns. Use a small view per cell type:

Column::make('status', 'Status')->view('tables.cells.status-badge')
{{-- resources/views/tables/cells/status-badge.blade.php --}}
<span class="badge badge-{{ $row->status === 'active' ? 'success' : 'secondary' }}">
    {{ ucfirst($row->status) }}
</span>

Filters as public properties, with debounce. Search and filter state belongs on the component as public properties bound with wire:model, so Livewire serialises and replays it correctly. Add a debounce to free-text search — wire:model.debounce.500ms on Livewire 2 — because each keystroke is otherwise a full server round-trip.

Compose rather than centralise. Use the table as a child Livewire component inside the page, and emit events up for anything the page owns (opening a modal, redirecting to edit). A table component that also handles creation forms, bulk edit, and settings becomes the thing you can never safely change.

Row actions should navigate, not mutate in place. Link to edit routes and route destructive actions through a controller or a dedicated action method with confirmation. Do not stretch this into a spreadsheet — bulk cell editing is explicitly outside its design envelope.

Gotchas and Tips

  • 0.x semver means minor bumps can break you. 0.10 → 0.11 is permitted to be a breaking change. Pin 0.10.3 exactly in composer.json, and add a CI check that fails if the resolved Livewire or Laravel version moves outside the range you tested.
  • Release notes are a compare link with no body. Treat every upgrade as unknown until you have read the diff. git log on the tag range plus a diff of src/ and resources/views/ takes ten minutes; upgrading blind costs a day.
  • N+1 queries are the most common real-world regression with this pattern. Every relation referenced in a column needs with(). Verify with Debugbar or DB::listen() on the heaviest screen before it ships, not after.
  • Sorting on relation or computed columns is where it breaks. user.name as a display column is fine; making it sortable() generally requires a join or a subquery orderBy. If a sort silently does nothing or errors, that is the cause.
  • Search across relations needs whereHas (correct) or a join (fast, but watch duplicate rows). Pick deliberately per screen.
  • Reset pagination when filters change — otherwise a user on page 7 filters down to two results and sees an empty table. Look for the page-reset method in the base component.
  • Bootstrap 4 only. If another part of the app is on BS5 or Tailwind, scope the package's views rather than trying to override globally.
  • wire:model vs wire:model.live — Livewire 2 and 3 use different syntax and deferral semantics. If you have mixed versions across components, this will bite on filters specifically.
  • Debug the round-trip, not the PHP. When a table "doesn't update," open the network tab and inspect the Livewire message request and its JSON response — that shows you whether the state is missing (hydration problem, usually a closure or non-serialisable property) or the query is wrong.
  • Exports will exhaust memory on large result sets. Chunk and queue them outside the Livewire request. Never stream a full unpaginated dataset from a component render.
  • Keep the exit plan cheap. Because usage is behind AppTable, swapping to Filament tables or an in-house implementation later is a bounded refactor. The thing that makes adoption expensive is not the package — it is letting Column::make(...) calls scatter across thirty components.
  • Pre-1.0, small maintainer base, no commercial support. Acceptable for internal admin and reporting surfaces. Revisit at 1.0 for anything with an SLA or a long support horizon.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log