Every Laravel project eventually needs the same thing: an admin page with a table, filters, a search box, an add/edit modal, and CRUD buttons. You
rebuild it from scratch every time — or you reach for a heavyweight framework that replaces your whole UI stack.
I got tired of both options, so I built MrCatz DataTable — a Livewire-first Laravel DataTable package that gives
you Filament-level CRUD productivity without taking over your Tailwind + DaisyUI stack.
In this post I'll show you how to go from composer require to a production-ready admin CRUD page in under 5 minutes.
What you get out of the box
- Pagination, sorting, column resize, column reorder, column visibility toggle
- Multi-keyword search with per-column relevance scoring + typo tolerance
- Filters (select, callback, date, date-range, dependent)
- Inline cell editing with validation
- Bulk select + bulk delete
- Expandable rows
- Excel + PDF export
- Form Builder with 25+ field types (text, number, date, file, rich editor, repeater, toggle, etc.)
- Modal form or full-page form view — switchable per page
- URL persistence for filters, sort, page, hidden columns
- Optional Meilisearch integration via Laravel Scout
- Tailwind CSS v4 + DaisyUI v5 styling — theme it however you want
All of it is opt-in. Turn off what you don't need, extend what you do.
Install
composer require mrcatz/datatable
That's it — no config publish required. Livewire 3, Laravel 10+, Tailwind v4.
Add the package's Blade files to your Tailwind source paths so its classes get compiled:
/* resources/css/app.css */
@source "../../vendor/mrcatz/datatable/resources/**/*.blade.php";
Your first CRUD page
An admin CRUD in MrCatz is always two Livewire components:
- A Page component (extends
MrCatzComponent) — handles the form modal, add/edit/delete lifecycle, notifications. - A Table component (extends
MrCatzDataTablesComponent) — defines columns, filters, and the base query.
Generate both with one artisan command:
php artisan make:mrcatz-table Product
Or write them by hand. Here's a complete product admin page in ~80 lines.
1. The Page component
namespace App\Livewire\Admin\Product;
use Illuminate\Support\Facades\DB;
use MrCatz\DataTable\MrCatzComponent;
use MrCatz\DataTable\MrCatzFormField;
class ProductPage extends MrCatzComponent
{
public $name, $sku, $price, $stock;
public $active = true;
public function mount()
{
$this->setTitle('Product');
}
public function render()
{
return view('livewire.admin.product.product-page');
}
public function setForm(): array
{
return [
MrCatzFormField::text('name', label: 'Name', rules: 'required|max:100', icon: 'inventory_2'),
MrCatzFormField::text('sku', label: 'SKU', rules: 'required|max:30', icon: 'tag'),
MrCatzFormField::number('price', label: 'Price', rules: 'required|numeric|min:0', icon: 'payments'),
MrCatzFormField::number('stock', label: 'Stock', rules: 'required|integer|min:0', icon: 'inventory'),
MrCatzFormField::toggle('active', label: 'Active'),
];
}
public function prepareEditData($data)
{
$this->id = $data['id'];
$this->name = $data['name'];
$this->sku = $data['sku'];
$this->price = $data['price'];
$this->stock = $data['stock'];
}
public function saveData()
{
$this->validate($this->getFormValidationRules());
$payload = compact('name', 'sku', 'price', 'stock') +
['active' => $this->active ? 1 : 0];
$this->isEdit
? DB::table('products')->where('id', $this->id)->update($payload)
: DB::table('products')->insert($payload);
$this->dispatch_to_view(true, $this->isEdit ? 'update' : 'insert');
}
public function dropData()
{
$ok = DB::table('products')->where('id', $this->id)->delete();
$this->dispatch_to_view((bool) $ok, 'delete');
}
}
Zero boilerplate for the modal — it's already wired up by MrCatzComponent.
2. The Table component
namespace App\Livewire\Admin\Product;
use Illuminate\Support\Facades\DB;
use MrCatz\DataTable\MrCatzDataTablesComponent;
use MrCatz\DataTable\MrCatzDataTableFilter;
class ProductTable extends MrCatzDataTablesComponent
{
public $showAddButton = true;
public $showSearch = true;
public $exportTitle = 'Products';
public function baseQuery()
{
return DB::table('products');
}
public function setTable()
{
return $this->CreateMrCatzTable()
->withColumnIndex('#')
->withColumn('Name', 'name', editable: true, sort: true)
->withColumn('SKU', 'sku', uppercase: true, sort: true)
->withColumn('Price', 'price', gravity: 'right', sort: true)
->withColumn('Stock', 'stock', gravity: 'right', editable: true)
->withActionColumn('Actions', editable: true, deletable: true)
->setDefaultOrder('name', 'asc');
}
public function setFilter()
{
return [
MrCatzDataTableFilter::create(
id: 'active',
label: 'Status',
data: [1 => 'Active', 0 => 'Inactive'],
key: 'active',
)->get(),
];
}
}
3. Wire them up
The Page view just mounts the Table + includes the form/scripts partials:
<div>
<livewire:admin.product.product-table />
@include('mrcatz::components.ui.datatable-form')
@include('mrcatz::components.ui.datatable-scripts')
</div>
Register a route:
Route::get('/admin/products', ProductPage::class)->name('admin.products');
Reload — you have a fully functional admin CRUD with search, filter, sort, pagination, inline edit, Excel/PDF export, and a modal form. All in ~100
lines.
Features you'll appreciate later
Inline editing with validation
->withColumn('Price', 'price', editable: true, rules: 'numeric|min:0')
User clicks a cell → input appears → Enter saves via a onInlineUpdate() hook on your Page component. Validation rules ship to the client automatically.
Full-page form mode for long forms
Some forms don't fit in a modal. One flag switches to a full-page view:
public $modalFullScreen = true;
The table hides, the form takes its place, and all table state is preserved (search, filters, sort, page, scroll) when the user returns.
Per-column search scoring
Boost matches in important columns:
public function configTable(): array
{
return [
'searchScoring' => [
'name' => 3, // 3x weight
'sku' => 2,
'tags' => 1,
],
'typoTolerance' => true,
];
}
Form Builder — 25+ field types
return [
MrCatzFormField::text('name'),
MrCatzFormField::select('category_id', data: $categories),
MrCatzFormField::date('published_at'),
MrCatzFormField::editor('description'), // Quill rich text
MrCatzFormField::repeater('variants', fields: [
MrCatzFormField::text('sku'),
MrCatzFormField::number('stock'),
]),
MrCatzFormField::file('thumbnail', accept: 'image/*'),
];
Meilisearch (beta)
public function configTable(): array
{
return ['useScout' => true];
}
Search offloads to Meilisearch; filters/sort stay on the database side. No other changes needed.
Live docs with interactive playgrounds
Every feature in the docs is live — not just code snippets. You can type in a real search box, flip filters, edit cells, open the form modal. Try it:
- Docs: datatable.catzoid.tech
- Demo: datatable.catzoid.tech/demo
- GitHub: github.com/mrc4tz/mrcatz-datatables
- Packagist: packagist.org/packages/mrcatz/datatable
Why another DataTable package?
- Yajra DataTables — jQuery-based, predates Livewire. Great if you're on Blade + jQuery.
- Filament — amazing, but it's a full admin framework. You adopt its UI, its panels, its conventions.
-
Livewire PowerGrid — closest alternative. MrCatz differs by shipping a Form Builder in the same package, a native full-page form mode, and
stricter adherence to DaisyUI (so theming is just
data-theme=).
MrCatz is for developers who want CRUD productivity without UI lock-in. Your Tailwind config, your DaisyUI theme, your Livewire flavor — MrCatz just
plugs into it.
If you try it out, I'd love feedback — GitHub issues, Reddit DMs, whatever. Star the repo if it saves you a few hours, and if you find a bug, the issue
tracker is open.
Happy CRUDing 🛠️
Top comments (0)