mrcatz/datatable
Opinionated DataTable + CRUD framework for Laravel Livewire. Build admin pages fast with pagination, sorting, filters, smart search, inline editing, bulk actions, expandable rows, exports, and a programmatic form builder. Includes Artisan scaffolding.
Installation
composer require mrcatz/datatable
Add the required directives to your base layout (resources/views/layouts/app.blade.php):
@include('mrcatz::components.ui.notification')
@livewireScripts
@stack('scripts')
Tailwind Configuration
Update resources/css/app.css to include:
@source '../../vendor/mrcatz/**/*.blade.php';
Generate a CRUD Page Scaffold a new admin page with:
php artisan mrcatz:make Product --path=Admin
This creates a Livewire component with a pre-configured DataTable.
Add a Route
Route::get('/admin/products', \App\Livewire\Admin\Product\ProductPage::class);
First Interaction
Visit /admin/products to see a fully functional CRUD interface with:
app/Livewire/Admin/Product/ProductPage.php to inspect the generated scaffold.resources/views/livewire/admin/product/product-page.blade.php for Blade template structure.Define columns in the columns() method of your DataTable class:
public function columns(): array
{
return [
Column::make('id', 'ID')
->sortable()
->searchable(),
Column::make('name', 'Name')
->editable()
->rules('required|max:255'),
Column::make('price', 'Price')
->format(fn($value) => '$' . number_format($value, 2))
->sortable(),
];
}
Add filters to your table:
public function filters(): array
{
return [
Filter::make('status')
->select([
'active' => 'Active',
'pending' => 'Pending',
'archived' => 'Archived',
])
->default('active'),
Filter::make('created_at')
->dateRange()
->label('Created Date'),
];
}
Enable cell-level editing with validation:
Column::make('description', 'Description')
->editable()
->rules('nullable|max:1000')
->validationMessage('Description must be less than 1000 characters.')
Add bulk operations:
public function bulkActions(): array
{
return [
BulkAction::make('delete')
->label('Delete Selected')
->icon('trash')
->color('error')
->action(function (array $ids) {
Product::whereIn('id', $ids)->delete();
$this->emit('toast', 'Products deleted successfully!');
}),
];
}
Define forms programmatically:
public function form(): Form
{
return Form::make()
->columns(2)
->section('Basic Information', function (Form\Section $section) {
$section->fields([
Text::make('name')
->required()
->label('Product Name'),
Text::make('price')
->type('number')
->required(),
]);
})
->section('Advanced Options', function (Form\Section $section) {
$section->fields([
Select::make('category_id')
->options($this->categories())
->label('Category'),
Checkbox::make('is_featured')
->label('Featured Product'),
]);
});
}
Set up exports (requires maatwebsite/excel and barryvdh/laravel-dompdf):
public function exports(): array
{
return [
Export::make('csv')
->label('CSV Export')
->icon('file-csv')
->action(function () {
return $this->exportToCsv();
}),
Export::make('pdf')
->label('PDF Export')
->icon('file-pdf')
->action(function () {
return $this->exportToPdf();
}),
];
}
MrCatzDataTables for core functionality.MrCatzPage for UI composition (e.g., ProductPage).@livewire('admin.product.product-page') to render.Leverage the query() method to customize data fetching:
protected function query(): Builder
{
return Product::query()
->with(['category', 'supplier'])
->when($this->search, function ($query) {
return $query->where('name', 'like', "%{$this->search}%")
->orWhere('description', 'like', "%{$this->search}%");
});
}
Use mount() for initialization logic:
public function mount()
{
$this->authorize('view', Product::class);
$this->with(['categories' => Category::all()]);
}
Customize DaisyUI colors in tailwind.config.js:
module.exports = {
daisyui: {
themes: [
{
mytheme: {
'primary': '#3b82f6',
'secondary': '#374151',
// ...
},
},
],
},
};
For remote data, override getData():
public function getData(): array
{
$response = Http::get('https://api.example.com/products');
return $response->json()['data'];
}
@include('mrcatz::components.ui.notification'), @livewireScripts, and @stack('scripts') are in your base layout.@source '../../vendor/mrcatz/**/*.blade.php' is in app.css and Tailwind is configured to scan these files.TypeError when using callback filters (e.g., createDateWithCallback).// Bad (returns void/null)
function ($query, $value) { $query->whereDate('foo', $value); }
// Good (returns builder)
function ($query, $value) { return $query->whereDate('foo', $value); }
Workaround: Use the updated applyBuilderCallback helper (v1.29.28+).whereIn/whereNotIn conditions in filters. Use the check export branch explicitly:
HasExport::buildExportQuery()->applyCheckFilter($filter, $activeValue);
h-8 (e.g., add h-8 to date inputs and adjust popover triggers).->validationMessage() to columns and ensure rules are properly defined:
Column::make('email')
->editable()
->rules('required|email')
->validationMessage('Please enter a valid email address.');
searchable()->meilisearch() modifier:
Column::make('name')->searchable()->meilisearch();
Override getQuery() to log SQL:
protected function getQuery(): Builder
{
$query = Product::query();
\Log::debug($query->toSql(), $query->getBindings());
return $query;
}
Dump filter states in updated():
public function updated($propertyName)
{
\Log::info('Filters:', $this->filters);
}
Log the export query before execution:
public function exportToC
How can I help you explore Laravel packages today?