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

Filament Resource Lock Laravel Package

blendbyte/filament-resource-lock

blendbyte/filament-resource-lock adds record locking to Filament resources, preventing users from editing the same record simultaneously. It shows lock ownership, blocks conflicting edits, and releases locks automatically, helping teams avoid overwritten changes.

View on GitHub
Deep Wiki
Context7

Getting Started

The package adds pessimistic "record is being edited" locking to Filament resources so two admins can't silently overwrite each other.

  1. Install and publish.

    composer require blendbyte/filament-resource-lock
    php artisan vendor:publish --tag="filament-resource-lock-migrations"
    php artisan vendor:publish --tag="filament-resource-lock-config"
    php artisan migrate
    

    This gives you a resource_locks table (the source of truth) and a config file controlling TTL/cleanup behaviour.

  2. Opt models in. Add the package's lockable trait to every model that backs a Filament resource you want protected (e.g. Post, Order). The trait wires up the lock relation and the isLocked() / lock() / unlock() helpers.

  3. Register the panel plugin. In your PanelProvider::panel() chain:

    ->plugin(ResourceLockPlugin::make())
    

    Confirm the exact class import against the README — it lives under Blendbyte\FilamentResourceLock.

  4. First use case to validate: open the same Edit page in two browser sessions. User A should acquire the lock on mount; User B should see the lock notice and find the form read-only (or be blocked outright, depending on config). Close A's tab and confirm B can take over after the TTL expires.

Look first at the published config (lock duration, cleanup cadence, whether the UI blocks or only warns) and at the trait — it's the smallest surface area and tells you what's hookable.

Implementation Patterns

  • Panel-level registration, model-level opt-in. Register the plugin once per panel; the trait is per-model. This lets you lock Invoice but leave low-risk Tag resources unlocked. Keep a checklist of resources where locking is required so new resources don't silently ship unprotected.
  • Programmatic guards. isLocked() is the primitive for anything non-UI: policy checks, custom Filament actions, API controllers that mutate the same records. Combine with abort_unless() or a Filament ->visible(fn ($record) => ! $record->isLocked()) on destructive actions.
  • Custom lock-aware actions. A common pattern is a "Force unlock" header action gated on Gate::allows('forceUnlockResource'), calling $record->unlock() and logging who broke the lock. Pairs well with an audit/activity package.
  • Table column for at-a-glance status. Add a TextColumn::make('lock')/icon column driven by the relation so list views surface which rows are currently being edited. Keep it toggleable() — it costs a relation load per row.
  • Multi-tenant / scoped resources. Since v2.2.1 the lock lookup is properly scoped, so locks on records in other tenants won't leak into isLocked(). If you were relying on the old behaviour (or worked around it with manual where clauses), drop those workarounds.
  • Scheduled cleanup. If the config supports it, schedule the cleanup command to purge expired rows; otherwise long-abandoned locks linger and show up as stale UI state.
  • Queued/console work. Locks are a UI concurrency mechanism, not a transaction guard. Jobs and imports should use DB transactions or lockForUpdate() — don't attempt to respect resource_locks from a queue worker.

Gotchas and Tips

  • Stale in-memory relation (fixed in 2.2.1). Unlocking now clears the loaded lock relation on the model instance. Before this, $record->unlock(); $record->isLocked(); could still return true because the relation was cached. On older versions, call $record->refresh() or unsetRelation() after unlocking — and still do so if you hold a different instance of the same model than the one that unlocked.
  • Livewire component state. Filament Edit pages are Livewire components holding a live model instance. If you unlock via a header action, re-render depends on that relation being cleared — another reason 2.2.1 matters. Test force-unlock flows explicitly.
  • isLocked() was previously an unscoped exists(). Before 2.2.1 it could return true based on any row in the locks table rather than the specific record. Symptom: every record appeared locked once one lock existed. If you're on ≤2.2.0 and see this, that's the bug — upgrade rather than patching around it.
  • TTL vs. real presence. Locks expire on a timer; they don't detect a closed tab. Set the TTL to something longer than a typical edit but short enough that an abandoned lock doesn't block the record for a day. There's no heartbeat in this package, so a long-running editor can outlive their own lock — decide whether that's acceptable or whether you need a refresh-on-save.
  • Unlock on save/close. If the package doesn't release the lock automatically on successful save, wire it into the edit page's afterSave()/afterCreate() hooks or a mount()/booted() cleanup, otherwise users see "locked" on a record nobody is editing.
  • Migrations are published, not auto-loaded. Forgetting php artisan migrate on deploy produces confusing "nothing locks" behaviour rather than an error. Add it to your deploy checklist.
  • Testing. Use RefreshDatabase and Carbon::setTestNow() to exercise TTL expiry deterministically. Assert both directions: assertTrue($a->isLocked()) for a locked record and assertFalse() for a sibling record of the same model — the second assertion is what would have caught the 2.2.0 scoping bug.
  • Extension points. The config file (TTL, cleanup, behaviour), the trait's overridable methods, and the lock notice view (publishable) are the intended customisation seams. Prefer overriding the trait methods or swapping the view over forking the package — the two 2.2.1 fixes were both inside isLocked()/unlock(), so patched copies will drift fast.
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.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
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