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 Tables Laravel Package

ramonrietdijk/livewire-tables

A Laravel Livewire package for building dynamic, responsive data tables with sorting, searching, filtering, pagination, and customizable columns and views. Drop it into any Livewire component to render powerful, interactive tables with minimal code.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require ramonrietdijk/livewire-tables
php artisan vendor:publish --tag="livewire-tables-config"
# only if you intend to restyle the markup:
php artisan vendor:publish --tag="livewire-tables-views"

The package assumes Laravel 10/11, PHP 8.1+, Livewire 3 and Tailwind (the default views are Tailwind classes, so a Bootstrap app means republishing and rewriting the view).

A table is a Livewire component that extends the package base class and names a model:

namespace App\Livewire;

use App\Models\User;
use RamonRietdijk\LivewireTables\Livewire\LivewireTable;
use RamonRietdijk\LivewireTables\Columns\Column;

class UserTable extends LivewireTable
{
    protected string $model = User::class;

    public function columns(): array
    {
        return [
            Column::make('id', 'ID'),
            Column::make('name', 'Name'),
            Column::make('email', 'Email'),
        ];
    }
}
<livewire:user-table />

That alone gives you pagination, global search, sorting and per-page selection — which is exactly the boilerplate it exists to remove.

Where to look first: vendor/ramonrietdijk/livewire-tables/src/Livewire/LivewireTable.php. That class is the contract — every public/protected method is an extension point. Then read src/Columns/Column.php for the fluent column API, and skim src/Filters and src/Actions so you know what ships before writing your own.

First use case: an admin index page you'd otherwise hand-roll. Replace the controller + Blade loop with one table class, keep your existing show/edit routes as column links.

Implementation Patterns

Columns cover the three cases you actually hit. Database columns by name, relations by dot notation, and non-SQL values as computed:

public function columns(): array
{
    return [
        Column::make('company.name', 'Company'),                     // eager-loads via dot notation
        Column::make('created_at', 'Created')->format(fn ($value) => $value->format('d-m-Y')),
        Column::make('is_admin', 'Admin')->asBoolean(),
        Column::make('full_name', 'Name')
            ->computed(fn (User $user) => "{$user->first_name} {$user->last_name}"),
    ];
}

Toggle behaviour per column rather than globally (->searchable(), ->sortable(), and their false variants). Leaving search enabled on a JSON or accessor column is the fastest way to get a confusing SQL error.

Scope the query, don't filter the results. Multi-tenancy, soft-deleted handling and "only published" belong in the query hook, so counts, pagination and bulk actions all agree:

protected function query(Builder $query): Builder
{
    return $query->where('team_id', auth()->user()->team_id);
}

Filters are cheap wins for the UI. Select/date/search filters keep state in the component and compose with the query, so you get "filter + search + sort + paginate" without writing a single if ($request->has(...)).

Actions replace ad-hoc buttons. Bulk delete, export, "mark as reviewed" — define them once with a callback receiving the selected models. Always re-authorize inside the callback: the UI flag hides the button, it does not protect the endpoint.

Wire it into your page three ways: as the whole page (Route::get('/users', UserTable::class)), nested in a Blade view for dashboard layouts, or embedded alongside other widgets. For the latter, give each table a distinct wire:key and its own wrapper element.

Testing. These are ordinary Livewire components, so Livewire::test(UserTable::class)->assertSee('...')->call('...') works. Assert on the query behaviour (scoping, filters applied) rather than on markup — the Blade is the part most likely to be republished and changed.

Gotchas and Tips

  • N+1 is the default failure mode. A dot-notation relation column on 25 rows is 25 queries unless eager-loaded. Check with Debugbar early; add the relation to the table's with/query rather than relying on lazy loading.
  • Computed columns cannot be searched or sorted in SQL. Searching falls back to whatever the package does with the raw attribute name, which usually returns nothing or errors. Keep computed() for display only.
  • Multiple tables on one page can collide on query-string state (sort, search, per-page). Give each a unique key, and verify the URL params don't overwrite each other.
  • Bulk actions and selection scope. Confirm whether "select all" means the current page or the filtered set before shipping a destructive action; the difference is a data-loss incident. Pair every destructive action with a confirmation step.
  • Tailwind purging. If you publish the views, or if your CSS pipeline scans only resources/, add the package's view path to content in tailwind.config.js — otherwise the table renders unstyled in production only.
  • Pagination theme. Livewire 3 defaults to Tailwind; a mismatch (or a Bootstrap app) produces unstyled or invisible pagination links. Set it explicitly in the config.
  • Hiding is not securing. Column visibility callbacks and action can-style flags are presentation. Any route or action the table exposes still needs a policy check server-side.
  • Name collision with rappasoft/laravel-livewire-tables. Both can coexist but the Column/Action/Filter imports become ambiguous fast. Pick one table package per project and import with aliases if you must.
  • Debugging: dump the builder inside query() (->dd()), inspect getQueryString() for state bugs, and use Livewire's devtools for request payloads. Most "it doesn't filter" reports are state not reaching the query, not the query being wrong.
  • Maintenance risk is real but manageable. 211 stars and a single-maintainer footprint mean you may end up forking. Pin the minor version, keep customisations in subclasses and published views (never edit vendor/), and re-read the CHANGELOG before upgrading — release activity as of the 2026-08-20 tag suggests it's alive, not abandoned.
  • Extension points, in order of preference: subclass Column/Filter/Action → override methods on your table class → publish and edit views → patch the package. Stay in the first two and upgrades stay boring.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager