Download the PHP package wezlo/filament-record-freezer without Composer
On this page you can find all versions of the php package wezlo/filament-record-freezer. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download wezlo/filament-record-freezer
More information about wezlo/filament-record-freezer
Files in wezlo/filament-record-freezer
Package filament-record-freezer
Short Description Freeze Eloquent records against modification — audit holds, finalized contracts, legal holds — for Filament.
License MIT
Informations about the package filament-record-freezer
Filament Record Freezer
Freeze individual Eloquent records against modification — finalised contracts, audited financial periods, legal holds — for Filament.
When a record is frozen:
- Any attempt to
update(),save()(with dirty attributes) ordelete()it throws aRecordFrozenException— from the UI, a job, a policy, ortinker. - Filament resources using the
HandlesFrozenRecordstrait have theircanEdit()/canDelete()disabled automatically, and row-level edit/delete actions are hidden viaFreezableActionGroup. - A polymorphic audit trail records who froze it, when, and why. Unfreezing preserves full history (freeze → unfreeze → re-freeze creates a new row each time) with who / when / reason for both sides.
Requirements
- PHP 8.2+
- Laravel 11+ / Laravel 13
- Filament v4+
Installation
The package is path-linked inside this monorepo. From the project root:
Register the plugin on the panel(s) where you want the admin resource:
Make a model freezable
Add the HasFreezes trait to any Eloquent model:
You can now:
Observer coverage: updating, deleting, and (for models using SoftDeletes) restoring.
Filament integration — FreezableActionGroup
The row-level UX is driven by FreezableActionGroup, a dropdown action group that bundles Freeze + Unfreeze alongside your own row actions. The host's actions (edit, delete, custom workflows) are passed through unfrozenActions() and are automatically hidden when the record is frozen — so there's no path to modifying a frozen record from the table.
canFreeze() and canUnfreeze() each accept a bool or a closure fn (Model $record, ?Authenticatable $user) => bool. Return false to hide that action for the current user / row. Default: allow.
HandlesFrozenRecords is still recommended as an authorization backstop — it overrides the resource's canEdit() and canDelete() so frozen records can't be modified even if a user hits the edit/delete URL directly or via a bulk action.
Audit trail — who froze / unfroze
Every freeze and unfreeze is recorded on the freezes table as an immutable row. Unfreezing does not delete the row — it sets unfrozen_at, unfrozen_by, and unfreeze_reason on the active row, preserving the full history.
Table schema:
| Column | Purpose |
|---|---|
freezable_type, freezable_id |
Polymorphic target |
frozen_by, frozen_at, reason |
Who froze, when, why |
unfrozen_by, unfrozen_at, unfreeze_reason |
Who released, when, why (null while active) |
Central admin resource
The plugin ships FreezeResource — a central list of every freeze, active or released, across every polymorphic model. Filters by status (active / released) and model type. Columns for both freeze and unfreeze metadata (reason, user, timestamps) are visible by default and toggleable. UnfreezeAction is available inline and only on active rows — released rows never show it, even when the underlying record has a newer active freeze.
Disable it from a specific panel:
Or change its navigation:
Configuration reference
Publish with php artisan vendor:publish --tag="filament-record-freezer-config".
| Key | Default | Description |
|---|---|---|
user_model |
App\Models\User |
Model used for frozen_by / unfrozen_by relationships. |
table_name |
freezes |
Name of the polymorphic table. |
ignored_columns |
[] |
Columns whose dirty writes are allowed even while frozen (e.g. updated_at). |
require_reason |
true |
Enforce a non-empty reason on freeze / unfreeze. |
min_reason_length |
5 |
Minimum reason length when required. |
resource.enabled |
true |
Whether the central FreezeResource registers itself. |
resource.navigation_group |
Compliance |
Navigation group for the resource. |
resource.navigation_icon |
heroicon-o-lock-closed |
Navigation icon. |
resource.navigation_sort |
90 |
Navigation sort order. |
Events
Subscribe to these events for notifications, mirroring, or analytics:
Wezlo\FilamentRecordFreezer\Events\RecordFrozen— a record just became frozen.Wezlo\FilamentRecordFreezer\Events\RecordUnfrozen— a record was just released.
Both carry the Freeze model (including the freezable morph target).
Low-level engine API
The engine performs no authorization of its own — it's a low-level primitive. Enforce permissions at the caller (action, job, command) before invoking it.
Translations
The package ships full translations for:
en(English)ar(Arabic)
Every user-visible string — action labels, modal headings, notifications, table columns, filters, tooltips — is routed through __('filament-record-freezer::freezer.…'). Switch locales via App::setLocale() or your existing locale middleware and the entire freezer UI follows.
Publish the translation files to override them in your host app:
Developer-facing exception messages (RecordFrozenException, engine validation) stay in English intentionally — they go to logs and stack traces, not end users.
Testing
The package is exercised by 27 Pest tests covering the trait, observer, engine, action group, resource, and re-freeze history semantics. Run them from the host app:
All versions of filament-record-freezer with dependencies
filament/filament Version ^4.0 || ^5.0
spatie/laravel-package-tools Version ^1.0