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 Excludable Laravel Package

maize-tech/laravel-excludable

Laravel Excludable adds a trait that lets Eloquent models be hidden from queries by default, so you can exclude records globally while still including them on demand via scopes.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Install with composer require maize-tech/laravel-excludable (4.1.0 adds Laravel 11 support; no breaking changes from 4.0.x, so an in-place composer update is safe).
  • Publish the config if you need to change the column name or default behaviour: php artisan vendor:publish --tag="excludable-config". Read config/excludable.php first — it tells you which column is used and how the scope is applied.
  • Add a boolean column to the table you want to filter, e.g. $table->boolean('excluded')->default(false)->index();. Index it: the global scope adds a WHERE clause to every query on that model.
  • Add the trait to the model (use Excludable;), add the column to $fillable if you plan to mass-assign it, and cast it to bool.
  • Backfill existing rows in a migration before enabling the trait on a table with data, otherwise nothing is hidden and you'll be debugging "why doesn't the scope work".
  • First use case: soft-hiding records without deleting them — archived products, retired users, deprecated categories. Default queries return only active rows; Product::withExcluded() in admin screens and Product::onlyExcluded() in a "trash"/archive view.

Where to look first: the trait (src/Concerns/Excludable.php), the global scope class, and the config file. Those three define the entire surface area.

Implementation Patterns

  • Admin vs. public queries. Keep the scope on by default and opt out at the boundary. In Filament/Nova resources or admin controllers, start the query with Model::withExcluded(). In public controllers, do nothing — the safe default is already applied.
  • Toggle helpers. Use the instance methods to flip state ($model->exclude(), $model->include()) rather than update(['excluded' => true]) in controllers; it keeps intent readable and pairs naturally with events/observers. Check state with isExcluded() in policies instead of comparing the raw column.
  • Bulk operations. Global scopes apply to update()/delete() too, so Model::query()->where(...)->delete() leaves excluded rows untouched. To reach them, prefix with withExcluded() explicitly — this is usually what you want, but be deliberate about it.
  • Relations and eager loads. The scope propagates into relationships automatically. Override per-relation: $post->load(['comments' => fn ($q) => $q->withExcluded()]), or define a dedicated commentsWithExcluded() relation for admin contexts.
  • Combining with SoftDeletes. Both are global scopes; they compose cleanly (onlyTrashed() does not also restore excluded rows). For an admin "everything" view, chain explicitly: Model::withExcluded()->withTrashed().
  • API exposure. Gate it behind a policy + query flag: if ($request->user()?->can('viewExcluded', Product::class)) { $query->withExcluded(); }. Treat the scope as a UX default, not a security boundary.
  • Validation rules. Rule::unique('products', 'sku') will ignore excluded rows and let duplicates through. Add ->withoutGlobalScope(...) (or build the rule from a withExcluded() query) when uniqueness must span the whole table.
  • Testing/factories. Seeders and factories generally want Model::withExcluded() (or withoutGlobalScopes()) so fixtures aren't silently invisible to assertions and assertDatabaseHas counts.

Gotchas and Tips

  • Silent filtering is the whole point — and the main footgun. Row counts, aggregates, exists(), pagination totals and find() will not see excluded records. If a number "looks low", check the scope before checking the query.
  • Route model binding 404s for excluded models. Correct for public routes, surprising in admin resource routes that weren't updated after enabling the trait.
  • Config caching. Run php artisan config:cache after editing excludable.php; a stale cache is the usual culprit when a column rename "does nothing". Remember to config:clear in tests.
  • Column type drift. MySQL returns 0/1; always cast to bool so isExcluded() and JSON serialisation behave across drivers (SQLite in tests vs. MySQL in prod).
  • Debugging the scope. ->toSql() shows the injected where "excluded" = ? — if it's absent, the trait isn't on the model or someone called withoutGlobalScopes() (which nukes SoftDeletes too). Prefer removing the single scope over removing all of them.
  • Naming collisions. A pre-existing excluded column with different semantics (nullable, enum, timestamp) will break in confusing ways — repoint the column in config or rename.
  • Mass-assignment pitfall. $model->update($request->all()) can let a client un-exclude a record if the column is fillable. Keep it out of $fillable and mutate it through explicit methods.
  • Extension points. The trait exposes the column name and scope behaviour for override — subclass/override in your own trait if you need per-tenant columns (e.g. excluded_for_tenant_id) rather than forking the package. withoutGlobalScope(SpecificScope::class) is the surgical escape hatch when you need one model/one query to bypass.
  • Don't use it for authorization. It's a query convenience; anything security-sensitive belongs in policies and explicit where clauses.
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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route