Installation:
composer require nalletje/livewire-tables
Publish the config (optional):
php artisan vendor:publish --provider="Nalletje\LivewireTables\LivewireTablesServiceProvider"
Basic Usage:
php artisan make:livewire Admin/UsersTable
Nalletje\LivewireTables\Traits\HasLivewireTables in your component:
use Nalletje\LivewireTables\Traits\HasLivewireTables;
class UsersTable extends Component
{
use HasLivewireTables;
public function tableQuery()
{
return User::query();
}
public function tableColumns()
{
return [
'id' => 'ID',
'name' => 'Name',
'email' => 'Email',
];
}
}
First Render:
<livewire:admin.users-table />
// In your Livewire component
public function tableQuery()
{
return User::query()
->where('active', true)
->orderBy('name');
}
public function tableColumns()
{
return [
'name' => 'Name',
'email' => 'Email',
'created_at' => 'Joined',
];
}
Result: A table with search, pagination, and sortable columns. Columns like created_at are auto-formatted.
Dynamic Query Modifiers:
Use tableQuery() to return a base query. The package handles:
search input in the UI)public function tableQuery()
{
$query = User::query()
->with('roles') // Eager load relations
->when($this->search, fn($q) => $q->where('name', 'like', "%{$this->search}%"));
return $query;
}
Filter Integration:
Define filters in tableFilters():
public function tableFilters()
{
return [
'active' => [
'type' => 'select',
'label' => 'Status',
'options' => [
'all' => 'All',
'active' => 'Active',
'inactive' => 'Inactive',
],
'default' => 'all',
],
];
}
Filter Logic:
public function tableQuery()
{
$query = User::query();
if ($this->active === 'active') {
$query->where('active', true);
} elseif ($this->active === 'inactive') {
$query->where('active', false);
}
return $query;
}
Single/Multi-Row Actions:
Define actions in tableActions():
public function tableActions()
{
return [
'edit' => [
'label' => 'Edit',
'icon' => 'fas fa-edit',
'handler' => fn($user) => redirect()->route('users.edit', $user),
],
'delete' => [
'label' => 'Delete',
'icon' => 'fas fa-trash',
'handler' => fn($user) => $this->deleteUser($user),
'confirm' => 'Are you sure?',
],
'bulk_delete' => [
'label' => 'Delete Selected',
'icon' => 'fas fa-trash',
'handler' => fn($users) => $this->deleteUsers($users),
'confirm' => 'Delete selected users?',
'multi' => true, // Enable for bulk actions
],
];
}
Form-Based Actions:
public function tableActions()
{
return [
'promote' => [
'label' => 'Promote',
'icon' => 'fas fa-user-shield',
'handler' => fn($user) => $this->render('promote-modal', ['user' => $user]),
'form' => true, // Renders a modal with form
],
];
}
Handler:
public function promoteUser($user, $role)
{
$user->roles()->attach($role);
return new Message('User promoted successfully!');
}
Transform Column Data:
public function tableColumns()
{
return [
'name' => 'Name',
'email' => fn($user) => \Illuminate\Support\HtmlString::from("<a href='mailto:{$user->email}'>{$user->email}</a>"),
'roles' => fn($user) => $user->roles->implode('name', ', '),
];
}
Conditional Columns:
public function tableColumns()
{
return [
'name' => 'Name',
'active' => fn($user) => $user->active
? '<span class="badge bg-success">Active</span>'
: '<span class="badge bg-danger">Inactive</span>',
];
}
Add buttons above/below the table:
public function tableButtons()
{
return [
'create' => [
'label' => 'Add User',
'icon' => 'fas fa-plus',
'route' => route('users.create'),
'class' => 'btn-primary',
],
'export' => [
'label' => 'Export',
'icon' => 'fas fa-file-export',
'handler' => fn() => $this->exportUsers(),
],
];
}
public function tableQuery()
{
return User::query()
->when($this->hasPermission('view-inactive-users'), fn($q) => $q->where('active', false));
}
Action Handler Returns:
Message object (not a string).
return new \Nalletje\LivewireTables\Message('Success!');
Message class.Relation Loading:
// Bad
return User::query(); // Missing `with('roles')`
// Good
return User::with('roles')->query();
Filter Defaults:
tableFilters() must match the query logic.
// If default is 'all', ensure your query handles it:
if ($this->active === 'all') {
// No where clause
}
Bootstrap 5 Dependencies:
@vite(['resources/css/app.css', 'resources/js/app.js'])
Ensure bootstrap@5 is included.Query Inspection:
Use Laravel Debugbar or dd($this->tableQuery()->toSql()) to verify queries.
Action Debugging:
Add dd($user) in handlers to inspect passed data:
public function deleteUser($user)
{
dd($user); // Debug payload
$user->delete();
return new Message('Deleted!');
}
Filter State:
Dump filter values in mount():
public function mount()
{
dd($this->active, $this->search); // Check current state
}
Custom Views: Override the default table view by publishing assets:
php artisan vendor:publish --tag=livewire-tables-views
Modify resources/views/vendor/livewire-tables/table.blade.php.
Add Select2 Filters: Extend the package by adding Select2 support:
public function tableFilters()
{
return [
'role' => [
'type' => 'select2', // Custom type
'label' => 'Role',
'options' => Role::pluck('name', 'id'),
'ajax' => true, // Enable AJAX for large datasets
],
];
}
Note: Requires manual JS integration (see Select2 docs).
Bulk Action Confirmation: Customize bulk
How can I help you explore Laravel packages today?