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

Laravel Auto Hard Deleter Laravel Package

sbamtr/laravel-auto-hard-deleter

Automatically hard delete soft-deleted Eloquent models after a configurable retention period. Add a trait to your models, set how long records should linger in the trash, and let Laravel's scheduler permanently purge them, with no manual cleanup jobs required.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a small utility that hard-deletes soft-deleted Eloquent models once they've been trashed for longer than a configured retention window. It is thin glue around Laravel's SoftDeletes trait and a scheduled cleanup job — not a full data-retention framework. Treat it as a convenience, not a compliance tool.

Minimal setup

  1. Install (it's abandoned/unmaintained, so pin or vendor a copy):
    composer require sbamtr/laravel-auto-hard-deleter
    
  2. Publish the config (check the exact tag in the package's README, likely auto-hard-deleter-config):
    php artisan vendor:publish --provider="SiavashBamshadnia\AutoHardDeleter\AutoHardDeleterServiceProvider"
    
  3. Open config/auto-hard-deleter.php and register the models you want purged with their retention in days. The shape is roughly a map of model class → number of days:
    'models' => [
        App\Models\Notification::class => 30,
        App\Models\AuditLog::class    => 365,
    ],
    
  4. Schedule the package's artisan command (run php artisan list | grep -i hard to confirm the exact signature — it's the only entry point) in app/Console/Kernel.php or routes/console.php:
    $schedule->command('auto-hard-deleter:run')->dailyAt('03:00');
    

Where to look first

  • The service provider: it tells you what command, config key, and bindings exist. Read it before assuming an API.
  • The published config file: this is the entire public surface area. If a model isn't listed there, the package will never touch it.
  • The command class: it will show you the query it runs (whereNotNull('deleted_at'), where('deleted_at', '<', now()->subDays($n))), how it chunks, and whether it force-deletes per model.

First use case

The cleanest first win is throwaway rows that users can already restore: abandoned draft records, read notifications, expired invitations/coupons. Point the config at one expendable model, set the retention to something conservative (30–90 days), run php artisan auto-hard-deleter:run in staging with --pretend if supported, then wire up the scheduler. Only after that do you add models with real auditing value.

Implementation Patterns

Config-driven registry as the single source of truth. The idiomatic use is: add a model to the config map, done. Resist the temptation to sprinkle hard-deletes elsewhere. Keep retention numbers in this file rather than hardcoding them in jobs so ops can tune them without a deploy.

Schedule it, don't event it. A natural instinct is to hook deleted and queue a delayed force-delete. Don't — that couples every ->delete() call to the package and loses the state if the queue is down. The package's scheduled sweep is the right pattern: idempotent, catch-up-friendly, and easy to verify. Run it nightly, off-peak, with withoutOverlapping() and onOneServer() if you run multiple app servers:

$schedule->command('auto-hard-deleter:run')
    ->dailyAt('03:00')
    ->withoutOverlapping()
    ->onOneServer();

Chunk and log per model. If the package doesn't already chunk, wrap it or fork the command. Large tables make a single DELETE ... WHERE deleted_at < ... a lock-heavy operation. Watch for per-model logging so you can see purge counts in your log aggregator — "how many notifications were purged last night" is a question you will be asked.

Per-tenant or per-environment overrides. The config is a plain PHP array, so read from env() or a tenant resolver:

'models' => [
    App\Models\AuditLog::class => (int) env('AUDIT_LOG_RETENTION_DAYS', 365),
],

For multi-tenant apps you'll usually outgrow the config map fast — one tenant's 30 days is another's 365. In that case keep the package for the trivial models and write a tenant-aware command for the rest; don't fight the package's shape.

Testing. Use Carbon::setTestNow() / $this->travel() to age rows, then assert the command purges old ones and leaves fresh ones:

$this->travel(-31)->days();
$notification = Notification::factory()->create();
$notification->delete();

$this->artisan('auto-hard-deleter:run')->assertSuccessful();

$this->assertDatabaseMissing('notifications', ['id' => $notification->id]);

Also test the negative: a record soft-deleted 10 days ago with 30-day retention must survive. That's the assertion that catches a botched config.

Integration tip. Pair it with a forceDeleting observer on the important models to emit an audit trail (who was purged, by which command run), because the package itself almost certainly just calls forceDelete() and moves on.

Gotchas and Tips

It's archived with zero dependents. Treat it as reference code, not infrastructure. The realistic long-term move is to copy the command (60–100 lines) into app/Console/Commands/HardDeleteStaleSoftDeleted.php and drop the dependency. You get the same behaviour, you own the bug fixes, and you avoid the abandoned-package conversation at the next security review. If you do keep it, pin the exact version in composer.json.

Config cache bites. php artisan config:cache will bake your retention map in. Any change to retention requires config:clear (or a re-cache in deploy). If ops "changes the retention and nothing happens," this is why nine times out of ten.

Soft-delete column assumption. The package almost certainly filters on deleted_at. If any of your models override getDeletedAtColumn() or use a custom timestamp column (e.g. archived_at), it will silently never purge them — or worse, compare against the wrong column. Verify per model.

Bulk deletes bypass everything. Model::where(...)->delete() on a soft-deleting model does set deleted_at, so those rows will be swept — good. But raw DB::table()->delete() or ->forceDelete() calls skip the package entirely. Know which deletions in your codebase go through Eloquent.

Foreign keys and cascades. A forceDelete() is a real DELETE; ON DELETE CASCADE and RESTRICT both apply, unlike a soft delete. A model that soft-deletes fine can throw Integrity constraint violation when the package hard-deletes it. Test the purge path against a realistic DB, not just SQLite in CI.

Restore race. A user restoring a record microseconds before the sweep can still lose it — the transaction boundary is whatever the package does, and it likely doesn't re-check deleted_at inside a lock. For user-facing models (accounts, invoices) don't include them; only purge things where loss is acceptable.

Timezone drift. now()->subDays($n) uses app.timezone. If your DB stores UTC and your app timezone is local, retention can be off by hours — meaningful if you have any "delete after exactly 24 hours" expectations.

Extension points are thin. Don't expect an event or interface to hook. If you need per-model pre-purge hooks, write your own command and call forceDelete() inside a DB::transaction() after a deleting-style guard. That's also where you'd add a dry-run flag, which the original likely lacks or implements crudely.

Debugging checklist when a row isn't purging: model actually in the config map with matching FQCN; deleted_at non-null and older than retention in the app's timezone; config cache cleared; scheduler actually running (php artisan schedule:list); no deleted_at column override; and the row isn't held by a FK RESTRICT. Those six cover nearly every case.

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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console