mpociot/reanimate
Reanimate adds a recycle bin to Laravel Eloquent models. Use the Reanimate trait to delete records and restore them later without adding soft-delete columns to your tables—deleted models are stored separately, keeping your schema clean while deletions stay undoable.
Architecture fit. Reanimate is an additive trait over Laravel's native SoftDeletes — no schema change, no data migration, no service-provider data plane. It slots cleanly beneath our own admin controllers, which is the right place for it: the package owns the data mechanics of bringing a record back, and owns nothing else. It is not an 80% version of a recycle bin. UI, per-role restore permissions, retention windows, bulk semantics, and audit all remain ours. That boundary is the most useful thing this evaluation produces, because it tells us the hard part of an "undo delete" roadmap item is governance and interface, not the database call.
The one genuinely non-trivial capability is the cascade: restoring a parent plus its soft-deleted children across Eloquent relationships. That is the fiddly part and the clearest justification for taking a dependency rather than writing it.
Integration feasibility. High, and cheap. composer require, publish, add the trait to an explicit allowlist of models, call restore inside a guarded controller. Roughly a day to a working spike. No migration on the soft-delete reading.
Technical risk. Four items drive the recommendation:
SoftDeletes and as keeping data available "without relying on soft deletes" (attribute snapshots in configurable storage). These are materially different products. If it truly persists snapshots, that is a second copy of sensitive data with its own retention, encryption, and erasure obligations — a compliance conversation, not an integration task. Read the source first; it is small enough to read end-to-end in minutes.Key questions.
illuminate/* and PHP constraints, and when was the last commit or release?route:cache?restored events we can hook for audit and downstream repair?slug, email) was reused after deletion?Stack fit. Laravel application already using SoftDeletes consistently. If soft deletes are sporadic across the schema, recovery becomes a per-entity special case rather than a coherent capability — deprioritize and fix the data model first. This package adds nothing on top of hard deletes and cannot recover what no longer exists.
Migration path. None required. Adoption is an allowlist decision, not a data migration: add the trait only to models where we have decided "delete is reversible here." Never on a shared base model — an allowlist is the whole point.
Compatibility. Confirm version constraints and release recency before committing. If the package predates our Laravel version, fork it (~50 lines) rather than installing under --ignore-platform-reqs. Verify route registration and route caching if applicable. Verify withTrashed() route model binding — binding silently 404s on trashed records otherwise, which is the most common "the package is broken" moment.
Sequencing.
auth + can:restore,invoice policy + explicit tenant scope + abort_unless($invoice->trashed(), 409), wrapped in DB::transaction(). Do not expose package routes directly.eloquent.restored: * listener for audit (actor, class, key, timestamp, request ID) and to repair everything that already treated the delete as final — search index, cache, denormalized counters, outbound webhooks.deleted_at null, children restored, audit row present.Maintenance. We own it from day one. Pin the version, vendor or fork the source, and set an explicit fork trigger (upstream dormancy, a Laravel major bump). Ongoing overhead is small but non-zero; the offset is that replacement cost is bounded and well understood, which keeps lock-in low.
Support. The measurable win is deflection of accidental-deletion tickets — count deletion-related tickets before and after and use the delta to justify or retire the feature. Support burden then shifts to governance: who is permitted to restore, what is restorable, and can we answer "who brought this back?" A restore is a production mutation and needs an audit trail the first time someone asks.
Scaling. Restore is a low-volume, admin-only, single-row-plus-cascade write — throughput is not the concern. The concerns are cascade depth (N children, deep graphs, polymorphic hops) and bulk operations (per-record outcomes, unique collisions, partial failure). Note that restore() bumps updated_at, so anything keyed to "last modified" now reports the record as changed today, which may be wrong for reporting.
Failure modes.
restore() on a non-trashed model returns true and does nothing — silently idempotent; guard with a 409 if the API should be honest.QueryException; decide up front to block, suffix, or fail loudly.restoring observer returning false aborts the restore — useful as a policy hook, a footgun when unrelated.Ramp-up. Low. The API surface is one trait and one call; an engineer integrates in a day. The actual learning — and where the estimate should sit — is the authorization model, per-relationship cascade semantics, and audit, not the package.
Recommendation. Adopt as a time-boxed spike or internal admin tool and as a validated reference, not as a permanent dependency on the sensitive-data recovery path. It buys a day and a clean answer to "is undo-delete worth building?" If the feature graduates, fork it or reimplement behind our own controller and policies and retire the dependency.
How can I help you explore Laravel packages today?