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

Boolean Softdeletes Laravel Package

laravel-json-api/boolean-softdeletes

Adds boolean soft delete support to Laravel JSON:API resources. Rather than a deleted_at timestamp, models are flagged with a boolean column, so trashed records stay visible and filterable through your API while normal queries still exclude them.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is glue, not a framework. It teaches laravel-json-api how to read the boolean soft-delete state produced by yocto/illuminate-database-softdeletes, so your JSON:API schemas can keep exposing filter[trashed], restore/force-delete actions, and clean serialization while the underlying column is a boolean instead of deleted_at.

With 3 stars and 0 dependents, treat the vendor source as the documentation. Before wiring anything, open vendor/laravel-json-api/boolean-softdeletes/src and read every file — it is an afternoon-sized audit and it is the only reliable source for trait names, namespaces, and supported filter values. The README's namespace examples are the first thing to distrust.

Install both halves together. The bridge does nothing on its own:

composer require yocto/illuminate-database-softdeletes
composer require laravel-json-api/boolean-softdeletes

1. Swap the trait on the model. Replace Laravel's native SoftDeletes with the Yocto trait on every model the bridge touches. Confirm the exact FQCN against your pinned Yocto version — this is a common source of Trait not found errors:

use Yocto\Illuminate\Database\SoftDeletes;

class Post extends Model
{
    use SoftDeletes;
}

2. Add the column. Boolean soft delete needs a default and (usually) an index. Confirm the column name Yocto defaults to in your pinned version before you write the migration — it is configurable and has moved between versions:

$table->boolean('is_deleted')->default(false)->index();

3. Register the schema concern. This is the only package-specific wiring step. Find the trait it ships in vendor/laravel-json-api/boolean-softdeletes/src and apply it to every schema whose model uses the Yocto trait:

use LaravelJsonApi\BooleanSoftDeletes\Schema\SoftDeletes; // verify against vendor src

class PostSchema extends Schema
{
    use SoftDeletes;
}

Your first use case should be a narrow, boring one: a single low-risk model with a list endpoint and a detail endpoint. Prove three things before anything else — GET /api/posts excludes deleted rows, GET /api/posts?filter[trashed]=with includes them, and a restore round-trip works end to end. If those three pass, the rest is configuration. If they don't, you have learned everything you need in a day rather than a sprint.

Where to look first, in order:

  1. vendor/laravel-json-api/boolean-softdeletes/src — the actual API surface.
  2. The Yocto trait's own scopes (withTrashed, onlyTrashed) — the bridge delegates to these, so weirdness there surfaces here.
  3. The filter[trashed] acceptance in your JSON:API validation/schema — this is where exposure bugs hide.

Implementation Patterns

The filter is the contract

filter[trashed] is the whole visible behavior of this package. Wire it in the schema's filters and validate it explicitly, because an unrecognised or misspelled value silently falling back to "include everything" is a data-exposure bug, not a UX bug:

public function filters(): array
{
    return [
        // Provided by the package's schema trait, or your own pass-through:
        // with / only / (absent = without)
    ];
}

Test all three states on every model: absent (default list, no deleted rows), with, and only. Assert on payload IDs, not counts, so a join or pagination bug cannot mask a leak.

Route binding trashed records

Restore needs to resolve a record that default route binding will 404 on. If the package does not handle this for you (check the source), the standard fix is a scoped binding or an explicit lookup in the action:

// routes/api.php — if you need explicit control
Route::get('/posts/{post}', ...)->withTrashed();

Prove it in a test: create → delete → PATCH /api/posts/{id} restore. A 404 here is the single most common integration failure and it is trivially preventable with one feature test.

Lifecycle actions

Keep the JSON:API action classes doing one thing each, and add authorization at the action level — trashed records stay addressable by ID, so retrieval is a real authorization surface:

// restore
$post = $this->model()->withTrashed()->findOrFail($id);

Gate::authorize('restore', $post);
$post->restore();
// force delete
Gate::authorize('forceDelete', $post);
$post->forceDelete(); // confirm the Yocto trait implements this, not just delete()

Model the transition, not the boolean

The boolean is a storage detail. Keep your policies, validation rules, and schema logic expressed in terms of "trashed" and "not trashed" so the day you switch column name or revert to deleted_at you are editing one place. Wrap Yocto-specific lookups behind a small base trait or scope on your own base model rather than scattering is_deleted across queries.

Seed your test suite with a shared contract

Because the package is unmaintained, encode its behaviour as tests you own. One abstract base test case applied to every soft-deletable schema covers: default exclusion, filter[trashed]=with, filter[trashed]=only, restore round-trip, force-delete, route binding of a trashed record, and authorization denial on trashed retrieval. That suite becomes your upgrade test when Laravel or laravel-json-api bumps a major version.

Migration workflow (brownfield)

If you are moving off native SoftDeletes, do it additively per model:

  1. Add the boolean column with a default and index.
  2. Backfill: UPDATE posts SET is_deleted = (deleted_at IS NOT NULL).
  3. Dual-write or shadow-read — populate both columns during the transition.
  4. Swap the trait and schema concern on one model.
  5. Cut over; drop deleted_at only after a rollback window.

Do not migrate all models in one PR. One model, one test, one deploy.


Gotchas and Tips

The package is source-available reference code, not a maintained library. With 3 stars and 0 dependents there is no community pressure to keep it in sync with Laravel or laravel-json-api releases. Vendor or fork it into your own repository, name an internal owner, and delete the composer require line once you have copied it. Apache-2.0 removes the license cost, not the ownership cost.

Namespaces in the README may not match the installed version. Verify every use statement against vendor/.../src before copying an example. A typo'd concern trait that silently no-ops is worse than a fatal error, because your default listings will quietly include deleted records.

A boolean loses who and when. If anyone asks "who deleted this and when" — moderation audits, compliance, support tickets — you need a separate audit table. Decide that before the migration, not after. This is the biggest semantic gap versus deleted_at.

Index selectivity is not a given. The whole performance argument for a boolean column is a smaller index, but a low-cardinality flag on a large table can produce an index the planner ignores or scans poorly. Run EXPLAIN on both the default list and the filter[trashed] variants against representative data volume before you commit to the model.

Default-list leakage is the highest-severity failure mode. It is a data-exposure bug. Add a test that asserts deleted IDs are absent from the default collection and keep it in CI forever.

Unique constraints collide with soft deletes. A boolean flag gives you no help here — unique(email) still blocks a new row while a "deleted" row exists. Plan for partial indexes or a composite (email, is_deleted) key, and confirm your database driver supports the partial index you want.

Config quirks live in Yocto, not here. The column name and the "deleted" value are configurable on the Yocto side. If your migration says is_deleted and Yocto defaults to deleted, every query silently matches nothing — records appear deleted when they aren't, or vice versa. Pin the Yocto version, set the config explicitly, and assert the column name in a test.

Relation cascades and withCount are yours to verify. Soft-delete scopes applied through the bridge may not propagate to eager-loaded relations or aggregate queries the way native SoftDeletes does. Test include= over trashed parents and any withCount in your resources.

Debugging checklist, in order of likelihood:

  1. Trait not actually applied to the model (check class_uses).
  2. Column name mismatch between migration and Yocto config.
  3. Schema concern not registered, so the filter is silently ignored.
  4. Route binding 404 on trashed records.
  5. restore()/forceDelete() not implemented by the Yocto version you pinned.

Extension points are the source file itself. If you need a different trashed filter value, a custom column resolution strategy, or onlyTrashed semantics that differ from the package, fork and edit the concern. Do not build a subclass layer on top of a package with no upgrade path — you will be maintaining the subclass either way, and the flat copy is easier to read.

Exit plan, written down. If upstream goes quiet or laravel-json-api ships a breaking change, your fallback is the vendored copy plus your contract tests. That is a survivable position precisely because the code is small — but only if you actually vendored it and wrote the tests while the spike was fresh.

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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums