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.
Columns automatically resolve joined table fields. Use dot notation (table.column) in make() and the engine handles search, sort, and display.
make('table.column') uses the qualified name for WHERE/ORDER BY in SQLselectAs() if explicitly settable_column, replacing . with _)column) if the derived alias is not present in the result rowselectAs(string) is available as an optional override for fully custom aliasesmake() argument |
SELECT alias in query | Works? |
|---|---|---|
users.email |
users.email as users_email |
✅ (auto-derived match) |
users.email |
users.email as user_email |
✅ (fallback to bare email) |
users.email |
(no alias, join only) | ✅ (fallback to bare email) |
user_email |
users.email as user_email |
✅ (direct attribute match) |
Note on sorting and searching: The field passed to
make()is always used as-is forORDER BYandWHEREclauses. Dot notation (users.email) produces qualified SQL — safe and unambiguous with joins. A plain alias (user_email) is used literally in SQL, which may cause ambiguous column errors with joins unless your database can resolve it.
use Livewire\Tables\Columns\TextColumn;
public function query(): Builder
{
return Product::query()
->join('categories', 'categories.id', '=', 'products.category_id')
->select('products.*', 'categories.name as category_name');
}
public function columns(): array
{
return [
TextColumn::make('products.name')
->label('Product'),
// Works: dot notation → sorts/searches as `categories.name`, display falls back correctly
TextColumn::make('categories.name')
->label('Category')
->sortable()
->searchable(),
];
}
The column categories.name will:
WHERE categories.name LIKE '%term%'ORDER BY categories.name ASCcategories_name on the row first; falls back to name if not presentpublic function query(): Builder
{
return Order::query()
->join('users', 'users.id', '=', 'orders.user_id')
->join('products', 'products.id', '=', 'orders.product_id')
->select(
'orders.*',
'users.name as user_name',
'products.name as product_name',
);
}
public function columns(): array
{
return [
TextColumn::make('orders.id')->label('Order #'),
TextColumn::make('users.name')->label('Customer')->sortable()->searchable(),
TextColumn::make('products.name')->label('Product')->sortable()->searchable(),
];
}
Here the SELECT uses user_name and product_name (not users_name/products_name). The display engine falls back to the bare column name (name) from each joined row automatically.
table.column dot notation in make() — this ensures unambiguous SQL for sort and searchtable.column first tries table_column, then falls back to columnusers.email as user_email), you can either:
make('users.email') — display will resolve through the fallback->selectAs('user_email') for absolute claritymake('user_email') when there is a join — while display may work, sorting and searching will use user_email literally in SQL which can be ambiguousHow can I help you explore Laravel packages today?