zofe/rapyd-livewire
zofe/rapyd-livewire integrates Rapyd’s Laravel CRUD toolkit with Livewire, helping you build reactive admin panels, forms, and data tables from reusable components. Define fields and grids once, then get dynamic, AJAX-driven interfaces with less boilerplate.
Install and publish first, code second.
composer require zofe/rapyd-livewire
php artisan vendor:publish --provider="Zofe\RapydLivewire\RapydLivewireServiceProvider"
Publishing is not optional — the package ships its own config (config/rapyd-livewire.php), Blade views, and CSS/JS assets. If a table renders as unstyled HTML with raw <th> tags, you skipped this step or your Vite/Tailwind setup is overriding the published Bootstrap CSS.
Where to look first, in order:
vendor/zofe/rapyd-livewire/src/Components/ — the base classes (DataTable, DataForm, DataGrid) are the API surface. Read them before the README; the docs are thin and the source is the real reference.vendor/zofe/rapyd-livewire/config/ — pagination size, view paths, asset paths, field-type registry.zofe/rapyd-laravel docs — much of the widget vocabulary (fields, columns, rules()) is inherited, and the older docs fill the gaps the Livewire wrapper doesn't document.Verify Livewire version compatibility before anything else. Check composer.json in the vendor directory. Livewire 2 and Livewire 3 differ on component hooks, event emitting ($emit vs $dispatch), and the mount()/boot() lifecycle. With 34 stars and a 0.29 score, assume the constraint may be older than your app.
First use case — a read-only DataTable. Pick a model you already have seeded. Create a dedicated Livewire component that extends the package's table class, point it at the model, add three columns, and mount it in a simple Blade view. Resist adding forms, custom buttons, or inline editing until the table paginates and searches correctly — that single step exercises config, views, assets, and Livewire wiring all at once, and it's where most setup failures surface.
use Zofe\RapydLivewire\Components\DataTable;
class UsersTable extends DataTable
{
public function mount(): void
{
$this->model = \App\Models\User::class;
$this->addColumn('text', ['name' => 'Name', 'sortable' => true]);
$this->addColumn('text', ['name' => 'Email', 'sortable' => true]);
$this->addColumn('text', ['name' => 'Created', 'sortable' => true]);
}
}
Then <livewire:users-table /> in a view that already has @livewireStyles and @livewireScripts.
Extend, don't configure from outside. The idiomatic pattern is one Livewire component per screen, extending the package's base class and configuring it in the appropriate lifecycle hook (mount() in most versions; check the base class for a build() or setup() hook). Keep the configuration declarative and close to the top of the method so the component reads like a spec.
DataTable — the workhorse. The patterns you'll reach for daily:
addColumn($type, ['name' => ..., 'sortable' => true]) for display columns.addSearch('text', 'email') / addFilter('select', 'role') for the search bar — search fields are declared separately from columns, and forgetting this is why your table "doesn't search" on a column it clearly displays.where, with) rather than trusting the model's default; table performance is query performance.DataForm — forms bound to a model. Fields are declared with a type and a name matching the database column or accessor, then chained with rules() for validation and modifiers for layout (placeholder, help text, default value). The save path is: declare fields → call the package's process/submit method in your save action → handle success and error callbacks. Keep your own $this->validate() out of it; the widget owns validation and will surface errors in its own markup. Redirect inside the success callback, not after the component method returns — Livewire will otherwise re-render the form with stale state.
DataGrid — inline editable tables. Use it when the edit interaction is genuinely tabular (bulk-editing prices, statuses, sort orders). For anything with more than ~6 fields or cross-field validation, a DataForm is less fighting and less fragile.
Compose table + form on one page. The most common real screen is a table with create/edit beside it. Two components, communicating via Livewire events:
// in the form's success callback
$this->dispatch('user-saved')->to(UsersTable::class);
and in the table, a listener that resets pagination and refreshes. This is cleaner than trying to make one component do both, and it keeps the form's validation state from leaking into the table's search state.
Custom cell rendering. For anything beyond plain text — status badges, avatar thumbnails, action dropdowns — use the column's closure/partial support rather than hacking the Blade template. Published views are a last resort: once you publish and edit rapyd's views, you own them across package upgrades. Prefer per-column overrides and keep a note of which views you've customized.
Authorization belongs in your component. Do the $this->authorize(...) or policy check in mount(), before any query runs. Don't rely on the widget to enforce visibility of row actions — hide the buttons yourself based on the same policy, or users will click buttons that 403.
Asset and CSS collisions are the #1 source of "it looks broken." The package assumes Bootstrap-era markup. Tailwind's preflight will strip list styles and table borders; a Vite pipeline that purges CSS will drop classes the package injects at runtime. If the table is functional but ugly, this is why — not a bug. Decide early whether you're isolating the table in a Bootstrap-styled layout or overriding the published views with your own markup.
Beware ambiguous column names on joins. Once you add a join() for a related name, sortable on a plain column name produces ORDER BY name and MySQL errors out. Qualify with the table name. Same for search columns.
Eager-load aggressively. The table renders every row on the page; a relationship accessor in a custom cell is an N+1 waiting to happen. Add with() in the component's query setup and check the query log on a 50-row page before shipping.
Validation is the widget's, not Livewire's. Don't mix $this->validate() with the widget's rules, and don't expect wire:model bindings on fields the widget renders. If validation messages aren't appearing, the rule string is usually wrong (Laravel pipe syntax) or the field name doesn't match the model attribute exactly.
Debugging checklist when a widget misbehaves, roughly in order of likelihood:
$fillable/$guarded blocking the save silently?dd() in a success callback swallowing the redirect?Mass actions are dangerous by default. Bulk delete has no built-in undo. Put a confirmation modal in front of every destructive mass action, and verify the selected-IDs state survives a pagination change — a classic bug is selecting rows on page 1, paging, and acting on a stale set.
Pin the version in composer.json and snapshot the widget output in a feature test. With a small maintainer base, a minor bump can change rendered markup or a lifecycle hook. A single test that asserts "the users table shows 10 rows and searches by email" is cheap insurance and will catch a breaking upgrade before your users do.
Extension points worth knowing: the field-type registry in the config (register your own money or enum field once instead of repeating options everywhere), the ability to swap a component's view per-instance, and the base component classes themselves — subclassing to add a shared trait (e.g. a standard date-format column, or a scoped-tenant query) is the cheapest way to keep a dozen screens consistent.
Finally: budget for reading source. A package at this adoption level won't have a Stack Overflow answer for your problem. Weigh that against the speed of getting a CRUD screen standing up in an afternoon — that trade is the entire case for using it, and it's a real one, as long as you go in expecting to be the one who debugs it.
How can I help you explore Laravel packages today?