Adding admin CRUD/restore to a plugin resource
Any tenant-scoped plugin model can get audited admin update / soft-delete /
restore with the shared scaffold. Requires the standard admin columns
(edit_count, deleted_at, deleted_by_id, delete_reason, edited_at,
edited_by_id) — already present on the plugin resource tables.
1. Trait on the model
The model must both use AdminManageable (which supplies the method bodies)
and implements AdminManageableModel (the contract the controller types
against). A trait cannot declare implements, so each adopting model states it
explicitly — this is what lets PluginAdminController call the admin methods
without a per-call @phpstan-ignore.
use App\Plugins\Concerns\AdminManageable;
use App\Plugins\Contracts\AdminManageableModel;
class Widget extends Model implements AdminManageableModel
{
use BelongsToTenant, HasStringUuid, AdminManageable;
}2. Admin controller
class WidgetAdminController extends \App\Plugins\Admin\PluginAdminController
{
protected function model(): string { return Widget::class; }
protected function rules(): array { return ['name' => ['sometimes', 'string', 'max:200']]; }
protected function permission(): string { return 'my-plugin.admin.manage.rooftop'; }
protected function procedurePrefix(): string { return 'my-plugin'; }
}3. Routes (in the plugin’s routes.php)
Route::put('/widgets/{id}/admin', [WidgetAdminController::class, 'update'])
->middleware('can:my-plugin.admin.manage.rooftop');
Route::delete('/widgets/{id}/admin', [WidgetAdminController::class, 'softDelete'])
->middleware('can:my-plugin.admin.manage.rooftop');
Route::post('/widgets/{id}/admin/restore', [WidgetAdminController::class, 'restore'])
->middleware('can:my-plugin.admin.manage.rooftop');4. Register the resource (in the ServiceProvider register())
\App\Audit\AuditableRegistry::register(Widget::class);This makes writes to the resource audited and lets the admin audit page
field-revert it. restore (un-soft-delete) is separate from that field-revert.
Note: registering a resource makes its entire Eloquent write lifecycle audited (any create/update/delete on the table), not only the admin actions — a bare Model::create() on a registered table now emits an audit row.