Search by

vortech / softdeletes-flag

papp-mate

Drop-in soft deletes for Laravel Eloquent that use an indexed boolean flag instead of a nullable timestamp, for faster queries in high-load applications.

Package info

github.com/Vortech-Group/softdeletes-flag

pkg:composer/vortech/softdeletes-flag

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-10-05 18:33 UTC

This package is auto-updated.

Last update: 2026-10-05 18:52:05 UTC


README

SoftDeletesFlag by Vortech

Tests Latest version Downloads PHP version Laravel 13 License

Drop-in soft deletes for Laravel Eloquent that use an indexed boolean flag
instead of a nullable timestamp, for faster queries in high-load applications.

Why

Laravel's built-in SoftDeletes filters every query with WHERE deleted_at IS NULL. SoftDeletesFlag swaps that for a plain, indexed boolean:

SoftDeletes SoftDeletesFlag
Column deleted_at TIMESTAMP NULL is_deleted BOOLEAN DEFAULT 0
Live rows filter deleted_at IS NULL is_deleted = 0
Index on a high-cardinality timestamp on a two-value flag
Knows when it was deleted yes no

If you need the deletion time, keep using SoftDeletes. If you only need to know whether a row is deleted, this package gives you the same API with a cheaper filter.

Requirements

  • PHP 8.3+
  • Laravel 13

Installation

composer require vortech/softdeletes-flag

The service provider is registered automatically through package discovery. Then run the install command to publish the config file:

php artisan softdeletes-flag:install

Use --force to overwrite an existing config file.

Usage

1. Add the column

Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->softDeletesFlag();
    $table->timestamps();
});

softDeletesFlag() adds an indexed boolean column that defaults to false. To remove it in down():

$table->dropSoftDeletesFlag();

This drops the index together with the column.

2. Add the trait

use Illuminate\Database\Eloquent\Model;
use Vortech\SoftDeletesFlag\Traits\SoftDeletesFlag;

class Post extends Model
{
    use SoftDeletesFlag;
}

That is all. If you know Laravel's SoftDeletes, you already know this.

API

$post->delete();        // sets is_deleted = true
$post->trashed();       // true
$post->restore();       // sets is_deleted = false
$post->forceDelete();   // really deletes the row

Post::all();                    // live rows only
Post::withTrashed()->get();     // live + trashed
Post::onlyTrashed()->get();     // trashed only
Post::withoutTrashed()->get();  // live only, explicitly

Post::onlyTrashed()->restore();            // bulk restore
Post::where('author_id', 1)->delete();     // bulk soft delete
Post::where('author_id', 1)->forceDelete(); // bulk hard delete

Post::restoreOrCreate(['title' => 'Hello']);  // restore a trashed match, or create
Post::createOrRestore(['title' => 'Hello']);  // create, or restore on a unique conflict

Events

trashed, restoring, restored, forceDeleting and forceDeleted are fired, just like with SoftDeletes. Returning false from restoring or forceDeleting cancels the operation.

Post::softDeleted(fn (Post $post) => Log::info("Trashed {$post->id}"));
Post::restored(fn (Post $post) => Log::info("Restored {$post->id}"));

Quiet variants are available too: restoreQuietly() and forceDeleteQuietly().

Route model binding

Implicit route model binding skips trashed models and returns a 404. Opt in per route with withTrashed():

Route::get('/posts/{post}', ShowPost::class);                      // trashed → 404

Route::get('/admin/posts/{post}', ShowPost::class)->withTrashed(); // trashed → resolved

Scoped bindings, custom binding fields ({post:slug}) and backed enums work as usual.

Configuration

The column defaults to is_deleted. To change it, edit the config file published by softdeletes-flag:install (or publish it manually with php artisan vendor:publish --tag=softdeletes-config):

// config/softdeletes-flag.php
return [
    'column_name' => 'is_deleted',
];

Set the name before running your migrations. Changing it later means renaming the column yourself.

Good to know

  • Unique indexes. A trashed row still occupies its unique value. Use restoreOrCreate() / createOrRestore() to bring the old row back instead of inserting a duplicate.
  • No deletion timestamp. Only updated_at changes on delete. Log deletions yourself if you need an audit trail.

Testing

composer test

The suite uses Pest and requires PHP 8.4+ to run.

Changelog

See CHANGELOG for what has changed recently.

Security

If you discover a security issue, please email mate@vortech.hu instead of using the issue tracker.

Credits

  • Mate Papp, Developer @ Vortech

License

The MIT License (MIT). See the License File for more information.

Vortech