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

androsamp/filament-resource-lock

Record-level locking for Filament v5 edit pages: others instantly see who owns the lock, are blocked from accidental overwrite, and can request handoff. Includes optional audit history with per-field diff and selective rollback, plus polling or Laravel Echo push updates.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. The package is a UI-layer collaboration guard for Filament v5 EditRecord pages, delivered as three opt-in traits/concerns (HasResourceLocks on the model, InteractsWithResourceLock on the edit page, HasResourceAudit on edit/create pages) plus two table columns and one config file. It is not a database lock, an optimistic-concurrency token, or an API-level write barrier — it is an advisory, TTL-based lease with a heartbeat. That distinction drives most of the risk discussion below: it protects the human editing path inside Filament, and nothing else. Any write that bypasses the panel (queues, imports, REST endpoints, console commands, forceFill in a job) will happily overwrite a locked record. It is therefore complementary to, not a substitute for, optimistic locking (updated_at/version column checks) in the persistence layer — and any team that reads "prevents accidental overwrites" as a data-integrity guarantee will be disappointed.

Within that scope the design is sound and appropriately narrow. Locks are stored in database or redis; transport is heartbeat (poll) or broadcast (Echo). Both axes are independently configurable, which is the right seam — it lets us start cheap (DB + polling) and upgrade without touching application code. The permission.save_and_unlock.* / permission.ask_to_unblock.* config with can()-based gates and a null bypass is a reasonable authorization hook that maps onto existing policy patterns.

Integration feasibility. Mechanically easy: one composer require, one install command, one migrate, two traits, two columns. There is no panel plugin registration, no service-provider wiring in application code. That is roughly a half-day for a competent Filament developer on a single resource. The friction is not the API surface; it is the stack prerequisites and the install command's side effects.

Technical risk. Several items would go on my risk register before any pilot:

  • Stack lock-in to a bleeding edge. php ^8.3, laravel ^12 || ^13, filament ^5 — this package is only usable by projects already on Filament 5, which materially narrows the addressable base (and mirrors the 0 dependents / 7 stars signal). It also means the package tracks Filament internals (mount, save, afterCreate, header-action injection) that are not covered by Filament's BC promise. A Filament 5.x minor could break it.
  • Audit can silently no-op. The README is admirably candid: overriding save() on the page replaces the trait's wrapper, so syncResourceAuditBeforeSave()/...AfterSave() must be re-invoked manually. Same for afterCreate(). Any team member who later customizes save() for unrelated reasons turns audit off without an error. That is a durability bug waiting to happen and needs an internal convention, a code-review checklist item, or a test that asserts audit rows appear after save.
  • Rollback authorization is unspecified. The audit slide-over restores field values through the package's service. The README does not state that rollback checks the resource's update policy, fires model events, or respects $fillable/mutators. Until verified, treat rollback as a privileged operation and gate it.
  • Trusted-HTML diff previews. HasAuditDiffPreview renders callback output as raw HTML; the README itself warns to escape. For any custom field showing user-controlled content, this is an XSS surface.
  • Edge cases still being discovered in patch releases. v2.1.2 fixed a UniqueConstraintViolationException when the same record is open in two tabs of one browser; v2.1.1 fixed a form refresh on post-wait lock acquisition. These are exactly the paths a pessimistic-lock UI must get right, and they were fixed within the last few releases. Expect more.

Key questions.

  1. Is there an admin/moderator "force release" path, or does a crashed tab leave a record locked until ttl_seconds expires (and can a user self-recover)?
  2. Does selective rollback run through Filament's policy layer and Eloquent events, or write directly?
  3. What is the test story — is there a CI matrix against Filament 5 minors, or only the Dependabot action bumps visible in the release notes?
  4. Is the lock table indexed and tenant-scoped, and does ResourceLockColumn in list tables issue per-row queries or eager-load?
  5. How does the broadcast driver behave when Echo is unavailable or channel auth fails — fail-open (editable) or fail-closed (locked)?
  6. Is audit.max_entries_per_resource the only retention control? What bounds total audit table growth?
  7. What is the maintainer's stated BC policy across 2.x, and is the repo accepting issues/PRs at a useful rate?

Integration Approach

Stack fit. Only relevant if the target panel is on Filament 5 / Laravel 12+ / PHP 8.3+. If we are on Filament 3 or 4, this package is not a candidate today and the correct answer is a shortlist deferral, not an integration. Assuming Filament 5: the package is a natural fit for admin panels with genuine multi-editor contention on the same records — operations consoles, order/customer management, CMS-style content, back-office config — and a poor fit for panels where one or two admins work disjoint queues (paying heartbeat/polling cost for a lock nobody contends).

Migration path. Greenfield within the existing app — no schema changes to existing tables; the install publishes two new tables (locks + audits) and a config. The steps I would sequence:

  1. composer require and pin to ~2.2 (not ^2.0) given the patch cadence.
  2. Review the install command's changes before running it. It publishes config, migrations, resources/js/filament-resource-lock/echo.js, and appends import './filament-resource-lock/echo'; to resources/js/bootstrap.js. That last step is an automated edit to application source. If we are on the polling driver, that import is dead code; either skip it (use the individual vendor:publish --tag= commands) or accept it and confirm the Vite build is clean.
  3. Migrate, then verify APP_URL is correct — the signed soft-release route (filament-resource-lock.release) fails validation silently if it isn't, which manifests as ghost locks.
  4. Traits on one model + one edit page; ResourceLockColumn on that resource's table only.
  5. Only after that resource is stable, add HasResourceAudit to edit and create pages, and wire getAuditHistoryAction() into header actions.

Compatibility. Two independent dependency surfaces to watch: (a) Filament 5.x minors — pin and test on upgrade; (b) the transport stack. broadcast requires a working broadcasting setup (Reverb/Pusher/Ably), Echo exposed on window, private-channel authorization, and a published echo.js kept aligned with broker/env. If we do not already run broadcasting, broadcast mode is not a config flip — it is a new piece of infrastructure.

Sequencing. Do not turn on all four capabilities at once. Order: (1) heartbeat + database — cheapest, no new infra, proves the UX; (2) add save_and_unlock and ask_to_unblock with real permissions defined; (3) add audit on edit pages, validate diffs render for our field types, confirm the save() override hazard does not apply to our page classes; (4) evaluate redis storage if lock QPS or latency demands it; (5) evaluate broadcast only if user-visible polling latency is an actual complaint. Rollback last, and only after the authorization question is answered.

Operational Impact

Maintenance. Bus factor is one, dependents are zero, and the package is at v2.2.1 with several behavioral fixes in the last few releases. Treat it as unsupported OSS: budget for reading the source, expect to fork or vendor-patch, and record the pinned version and the reason for it. The integration surface is small enough (two traits, two columns) that a fork is genuinely viable. Encapsulate usage so a swap-out is a single PR: no direct calls to package internals outside the traits, config read through our own wrapper if we touch it in multiple places.

Support. No vendor SLA. Internal runbook needed for the two support tickets this will generate: "I'm locked out of a record I need to edit" (resolution: wait for TTL / have the owner save-and-unlock / admin force-release if it exists) and "the lock indicator is wrong" (resolution: check heartbeat polling, APP_URL/signed route, Echo channel auth). Localization ships en and ru only — any other locale needs translation work under filament-resource-lock::resource-lock.*.

Scaling. Two cost centers. (1) Transport: heartbeat polling is concurrent editors / interval requests against the app and the lock store — trivial at tens of editors, non-trivial at hundreds, and it is the reason redis storage exists. (2) Audit: each save writes a full previous-state and new-state snapshot plus a computed field diff. Storage grows with saves × record width, and max_entries_per_resource caps per-record history but not total table size — a long-tail of many lightly-edited records grows unbounded. Plan a global retention job and think about snapshot size on wide records with rich text/JSON fields. Also note audit snapshots duplicate PII into a second table; that table must be in the data-retention and erasure scope, not just the source tables.

Failure modes. Ranked by what the user sees:

  • Heartbeat starvation in background tabs. Browsers throttle timers in inactive tabs; a user who leaves the edit page open returns to a lock that expired and was claimed by someone else, or holds a lock they think is live. Mitigation: ttl_seconds comfortably above the poll interval and an explicit "your lock expired" surface (verify one exists).
  • Ghost locks. Soft release depends on a signed route and correct APP_URL; a mismatch or a hard tab-close in poll mode leaves the record visually locked until TTL. Users perceive this as the tool being broken.
  • Fail-open on store outage. If Redis or the DB is unavailable, determine whether everyone becomes editable (fail-open — data-integrity regression, silently) or everyone is locked (fail-closed — availability incident). This must be a known, documented, alerted behavior, not a discovery during an outage.
  • Broadcast channel auth failure. Users never receive lock events: two editors both believe they hold the lock. This is the worst failure shape because it defeats the feature's entire purpose without any visible error — alert on channel subscription failures, and consider a polling reconciliation pass even in broadcast mode.
  • Audit silently disabled by a future save() override — detected only by the absence of history, so add a test that asserts an audit row is written after save.
  • Multi-tab same-browser edge cases — the v2.1.2 unique-constraint fix shows this path was broken recently; regression-test it.

Ramp-up. Developer ramp is low: the README is unusually complete for a 7-star package, including a candid section on the save() override limitation and a worked custom-field diff example. Estimate half a day to integrate on a pilot resource, one to two days to configure permissions, validate diff renderers against our actual field set, and write the guard tests. User ramp is the larger cost: editors need a two-minute explanation of lock ownership, request-unlock, and save-and-unlock, or the first response to a disabled form will be a support ticket. Ship it to one resource with a small, known user group first, watch lock contention and ghost-lock reports for two weeks, and only then roll out — and keep the rollback-to-no-lock path (remove one trait) documented and tested, because with a single-maintainer dependency that is the real exit strategy.

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.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log