kenepa/resource-lock
Kenepa Resource Lock is a Filament plugin that prevents concurrent edits by locking records while someone is editing. It shows who holds the lock, displays friendly warnings, and supports configurable lock duration and authorization policies.
kenepa/resource-lock adds per-record edit locking to Filament panels so two admins can't open the same row in an edit form and clobber each other. Version 4.0.0 is primarily the Filament 4 compatibility release — treat it as the "I'm on Filament 4" branch, and stay on 3.0.1 if you're still on Filament 3.
Minimum steps to get a first lock working:
composer require kenepa/resource-lock:^4.0
php artisan vendor:publish --tag="resource-lock-migrations"
php artisan vendor:publish --tag="resource-lock-config"
php artisan migrate
use Kenepa\ResourceLock\Concerns\HasResourceLock;
class Post extends Model
{
use HasResourceLock;
}
app/Providers/Filament/AdminPanelServiceProvider.php) so the lock state is wired into the panel:->plugin(\Kenepa\ResourceLock\ResourceLockPlugin::make())
use Kenepa\ResourceLock\Actions\ResourceLockAction;
public function table(Table $table): Table
{
return $table->actions([
ResourceLockAction::make(),
]);
}
Where to look first:
config/resource-lock.php in your app — this is the real API surface (lock duration/expiry, notification behaviour, icon/label customisation).If you're upgrading rather than installing, the first thing to verify is that your filament/filament constraint is already ^4.0 and PHP/Laravel meet Filament 4's floor (PHP 8.2+, Laravel 11+). Resource-lock 4.0.0 has no reason to be on a Filament 3 panel.
Lock on the edit page, not just the table. The table action is the demo; the day-to-day value is blocking the edit form itself. Wire the lock into EditRecord (and ViewRecord if you want read-only enforcement) so the form renders disabled/read-only when another user holds the lock, and release on save or navigation away. Centralise this in a base page class or a trait so you don't duplicate it across 40 resources.
Treat the lock as advisory in the UI and authoritative in the backend. The table/action layer hides or disables buttons, but a determined user can still hit the Livewire endpoint. If concurrent edits are a real data-integrity concern, pair the lock with optimistic concurrency on the model (updated_at check on save, or a lock_version column) so a stale write fails loudly instead of silently winning.
Refresh the lock state. Locks taken after the page loaded won't appear until the component re-renders. For teams that genuinely collide, add ->poll('10s') on the edit page (or a targeted $refresh listener) so the "locked by X" state propagates. Keep the interval generous — polling every second on a wide table is a cheap way to melt your queue-less DB.
Multi-panel / multi-tenant setups. A lock is scoped to a record, not a panel or tenant. If the same model is edited from an admin panel and a customer-facing panel, decide deliberately whether they share locks (usually yes) and make sure the lockable model's connection is correct — the package stores locks in the app DB, so a tenant-per-database architecture needs the table replicated or the connection overridden.
Pair it with Filament's own affordances. Resource-lock tells you who is editing; Filament's ->disabled(fn ($record) => ...) and ->visible() give you the presentation. Compose them: a TextColumn showing a lock badge + a disabled Edit action + a "force release" action gated behind a policy for admins.
Testing pattern. Two actingAs() users, acquire a lock with user A, assert user B's table action is disabled and that user B's save is rejected. This is the single most valuable regression test you can write around this package, and it's the one thing the README won't give you.
Audit/ops integration. Locks are a support burden, not just a UX feature. Surface "who locked this and when" in your admin tooling, and add your own php artisan wrapper or scheduled job that clears expired locks if you don't want to rely on lazy expiry — stale locks from closed tabs are the #1 complaint you'll get.
--force the config and compare against the new default with diff. Filament 4 reshuffled a lot of action/schema namespaces; if you published views or extended the action class, your subclass may now be extending a moved parent. php artisan filament:upgrade plus a cache clear (config:clear, view:clear, optimize:clear) covers most "it renders blank" reports.config/resource-lock.php, a user who closes their laptop mid-edit blocks that record indefinitely. Set expiry short (minutes, not hours) and document the manual release path for whoever gets the support ping.resource_locks table; add a prune to your scheduler.RefreshDatabase handles it only if the migration is actually published and committed. CI that installs from a fresh clone and runs migrate will fail silently-late if the publish step was manual on someone's laptop.^4.0 is fine, but read the changelog for the first month before bumping in a regulated codebase.How can I help you explore Laravel packages today?