laravel-model-flags

Add lightweight, queryable boolean flags to Eloquent models without adding a migration column for every piece of state you need to track.

Library
Composer
v1.5.0
456stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
45/100Fair
Development Activity24
Maintenance36
Community40
Maturity52
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
63/100Good
Architecture78
Code Quality85
Innovation45
Learning Curve45

Laravel Model Flags is a small Spatie package that lets any Eloquent model carry named boolean flags without a schema change. A single HasFlags trait wires the model up to a polymorphic flags table, so you can mark a record as flag()’d, check it with hasFlag(), and remove it with unflag() — all backed by a real, queryable relation instead of a JSON blob or a growing set of nullable columns.

The primary use case is idempotency: making long-running or resumable operations safe to re-run. A common example is an Artisan command that emails every user — by flagging each user after a successful send and scoping the next run to notFlagged(), a crashed or cancelled job can be restarted without re-sending mail to anyone who already received it. The same mechanism works for tracking migration/backfill progress, one-time onboarding steps, or any per-model “has this happened yet” state.

Beyond the basic flag/unflag pair, the package exposes flagged()/notFlagged() query scopes for filtering models by flag state, flagNames() to list all flags on a model, and latestFlag()/lastFlaggedAt() to inspect when a flag was last touched (re-flagging updates the timestamp rather than duplicating the row). Flags accept plain strings or PHP backed enums, and are automatically cleaned up via a deleted model event when the owning model is removed.

What You Get

  • A HasFlags trait that adds flag(), unflag(), and hasFlag() methods to any Eloquent model
  • flagged() and notFlagged() query scopes for filtering models by flag presence
  • flagNames(), latestFlag(), and lastFlaggedAt() for inspecting a model’s current and historical flag state
  • Support for both plain strings and PHP backed enums as flag names
  • A published migration and config file (via spatie/laravel-package-tools) so the flags table and flag model are customizable
  • Automatic flag cleanup on model deletion via a bootable model event

Common Use Cases

  • Making an Artisan command that emails or processes every user resumable after a crash, by flagging each record once handled
  • Tracking one-time onboarding or setup steps per user without adding a boolean column per step
  • Recording backfill or data-migration progress per row so a long-running script can be safely restarted
  • Marking records for later review or follow-up (e.g. “needs manual check”) without a dedicated status column
  • Auditing when a particular event last happened on a model via lastFlaggedAt()

Under The Hood

Architecture The package is intentionally small: a single HasFlags trait mixed into consumer models, a Flag Eloquent model with a morphTo flaggable relation, and a ModelFlagsServiceProvider that registers the config and migration through Spatie’s shared laravel-package-tools base class. The trait itself is the whole feature surface — flags() defines the morphMany relation (model class configurable via config('model-flags.flag_model')), and flag()/unflag()/hasFlag() are thin wrappers around firstOrCreate/whereIn/exists calls against that relation. A bootHasFlags() hook registers a deleted model event to cascade-delete flags when the owning model is removed, so there’s no orphaned-row cleanup job needed. Because everything hangs off Eloquent’s own relation and boot-trait mechanisms, the package adds no runtime services, queues, or background processes — it changes behavior only through the trait a consumer opts into.

Tech Stack Pure PHP 8.1+ targeting Laravel’s illuminate/contracts across a wide compatibility range (^9.0 through ^13.0), so it works with several concurrent Laravel LTS/current releases. It depends on spatie/laravel-package-tools for service-provider boilerplate (config/migration publishing) rather than hand-rolling that logic. Dev tooling is Pest (versions 1 through 4, again spanning multiple majors) with the pest-plugin-laravel extension, Orchestra Testbench for an isolated Laravel test app, spatie/test-time for freezing time in timestamp-sensitive tests, and Laravel Pint for style enforcement — all fairly standard for a Spatie-maintained Laravel package.

Code Quality Tests are thorough for the package’s scope: HasFlagsTest.php and FlagTest.php cover flagging, unflagging (single and array forms), the flagged/notFlagged scopes, flag-name listing, latest-flag lookup, timestamp updates on re-flagging, and cascade deletion on model removal — using Pest datasets to parameterize over string and enum flag names. TestCase boots a real Testbench Laravel app with an in-memory test_models table so relations are exercised against a live Eloquent stack rather than mocks. CI runs the test suite via GitHub Actions (run-tests.yml) and auto-fixes style issues on push via a Pint-based workflow, plus a changelog-update workflow — a lightweight but complete quality gate for a package this size. There’s no static analysis (PHPStan/Psalm) configured.

What Makes It Unique The package doesn’t invent a new concept — Laravel already has patterns for tagging and status columns — but it deliberately optimizes the flag/unflag/hasFlag API around the idempotent-batch-job use case: a notFlagged() scope paired with a per-record flag() call is a very small amount of code to make an otherwise all-or-nothing bulk operation safely resumable. Supporting backed enums alongside plain strings, and tracking per-flag updated_at timestamps (rather than just existence) so lastFlaggedAt() works, are the two specific design choices that differentiate it from simply adding a boolean column.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search