Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Resource Lock Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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
  1. Make your model lockable. Add the package trait to the Eloquent model behind the resource (not the Filament resource class):
use Kenepa\ResourceLock\Concerns\HasResourceLock;

class Post extends Model
{
    use HasResourceLock;
}
  1. Register the plugin in the panel provider (app/Providers/Filament/AdminPanelServiceProvider.php) so the lock state is wired into the panel:
->plugin(\Kenepa\ResourceLock\ResourceLockPlugin::make())
  1. Add the lock action to the resource table — this is your first use case and covers 90% of installs: a row action / column that shows who holds the lock and lets the current user acquire or release it.
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).
  • The published migration — confirm the locks table exists and is migrated on every environment, including your test DB.
  • The package's own test suite / PR #68 ("Improvements") if you rely on anything beyond the out-of-the-box action.

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.

Implementation Patterns

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.

Gotchas and Tips

  • This is a major bump because Filament is. There's no deprecation layer between 3.x and 4.0.0 — the plugin was retargeted at Filament 4. If you're mid-upgrade, upgrade Filament first, then resource-lock, and don't try to run both majors in the same app (composer will resolve one or the other).
  • Check your published files after upgrading. Re-publish/--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.
  • Stale locks are the default failure mode. If you don't set a sane expiry in 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.
  • Lock cleanup is lazy. Expired rows typically stay in the table until something touches them. Don't be surprised by a growing resource_locks table; add a prune to your scheduler.
  • Migrations in tests. 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.
  • Debugging "the button doesn't appear": in order — plugin registered on the panel (not just the provider), trait on the model, migration run, and then the action added to the table. Nine times out of ten it's the panel plugin registration, which produces no error at all.
  • Don't put the trait on a model shared across apps that point at different databases — the lock table lives on that model's connection.
  • Extension points: the action class and the config are where you customise; the lock acquisition itself is deliberately thin. If you need "lock only after first keystroke" or "lock the whole relation, not the record", build that on top rather than forking — the internals are small and the fork cost is real.
  • Pin the minor. With a Filament major behind it, expect rapid 4.0.x patches; ^4.0 is fine, but read the changelog for the first month before bumping in a regulated codebase.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource