juststeveking/laravel-feature-flags
A lightweight, database-backed feature flag package for Laravel. Define flags, enable or disable them from code or Artisan commands, and gate routes, Blade views, and application logic through a clean facade. Simple to install and easy to drop into existing Laravel projects.
Architecture fit. The package is a global, in-process feature-flag store for Laravel: a config file, a features database table, a bindable Feature model, and evaluation surfaced through a facade, a helper, Blade directives, and route middleware. The TPM framing question is not whether that design is clean — it is — but whether the flag layer belongs inside the application at all. For a Laravel monolith whose flag consumers are exclusively PHP (controllers, jobs, Blade, middleware), it fits cleanly and keeps a third-party vendor out of the request path and off privacy/sub-processor review. It does not fit if flags must be read by mobile clients, edge workers, or non-PHP services, and it is explicitly not a segmentation engine — it answers "is this on for everyone right now?" not "is this on for this user or account?" The latter is Laravel Pennant's territory or a custom resolver, and conflating the two is the most common and most expensive misread of this package. Treat the small surface area as a deliberate scope decision — small enough to read end-to-end, audit, and eventually delete — not a gap to be closed later by configuration.
Integration feasibility. High, and the real effort is process rather than code. The path is standard Laravel: Composer require, publish provider assets (config and migration — order matters), migrate, create rows via Artisan, then branch on the facade at consumption points. Two obligations should be scheduled explicitly rather than discovered in production: (1) flags are database rows, so every fresh environment and CI run starts with everything off unless a deploy-time seeder or idempotent firstOrCreate is wired in; (2) flag state is cached, so a toggle may not take effect until the relevant cache is cleared. Neither is a defect — both are inherited obligations, and together they explain the majority of "the flag isn't working" tickets. Bake both into the integration checklist on day one.
Technical risk. Moderate-low, and weighted toward maintenance and governance rather than correctness. The 1.4.0 release (2024-03-13) is a Laravel 11 compatibility update contributed by a community PR — a positive ecosystem-alignment signal, but a single data point, not a demonstrated maintenance cadence. Risks a TPM should carry on the register:
Key questions. Does 1.4.0 require PHP 8.2+ to align with Laravel 11? Are evaluations cached per-request, per-process, or per-TTL — and how does that behave under Octane or long-running queue workers, where a stale process could hold a flag state indefinitely? What is the maintainer's stated policy on future Laravel majors? Does the published migration timestamp risk colliding with ours? Who owns flag cleanup, and what is the expiry convention? Do we need audit history or experiment statistics now, or is that a later, separate buy? Will flags ever need to be consumed outside PHP — and if that is plausible within twelve months, is adopting this a one-way door we are walking through too early?
Stack fit. Strong for Laravel 10/11 monoliths and modular Laravel services already on Composer and using Laravel's own config and cache primitives — the package inherits the framework's operational model instead of introducing a new one, which is the whole point. Weak for polyglot estates, very high evaluation frequency, or anything needing exposure logging and significance testing. Frame the decision three ways at adoption time: build it ourselves (flag table, admin surface, cache invalidation, authorization — multi-week build plus permanent ownership), take this package (near-zero licensing cost, in-process, MIT), or buy a SaaS vendor (contract, procurement, privacy review, external calls on the hot path, per-seat pricing). For a Laravel monolith with modest flag volume, this is the credible middle option and should be the default candidate; vendors earn their keep only at real audit/governance/experimentation scale.
Migration path. Two distinct migrations are in play and must be sequenced separately. The package upgrade (1.3.x → 1.4.0) is a minor, backward-compatible dependency bump: run it on a branch, then regression-test flag resolution specifically with config cached, since that is where stale-state behavior hides. The adoption (no flags → flags) is the larger change: inventory candidate features, agree naming and default-state conventions, add the deploy-time seeder, and only then begin gating behavior. For destructive launches the order is fixed and non-negotiable — migrate schema, then toggle the flag, then remove the flag. Flipping before the migration ships is a guaranteed 500. Removal is a real phase, not an afterthought: schedule it at flag creation with an owner and expiry date, or the flags become permanent dead toggles that nobody dares delete.
Compatibility. Laravel 11 support landed in 1.4.0, which closes the previously blocking objection and is the reason this decision is live now. Confirm the PHP floor matches our runtime, verify the package's service provider and published config against our existing config cache, and test one flag end-to-end in a staging environment that mirrors production caching before rolling to the broader codebase. For services still on Laravel 10, the same 1.4.0 release is the last required bump; for anything older, treat framework upgrade as a prerequisite, not a parallel workstream.
Sequencing. Pilot on one low-risk, off-by-default feature on a single Laravel 11 service — ideally a user-visible change with a clean kill-switch story — and run the full lifecycle through it: seed, toggle on in staging, toggle on in production, monitor, toggle off, then remove the flag. That pilot is where naming, ownership, authorization, and cleanup conventions get proven or thrown out. Only after a clean pilot pass should the package be standardized across services, and only then with a written convention doc (naming scheme, owner-per-flag, expiry date, seeder requirement, flip authorization) rather than tribal knowledge.
Maintenance. Low-to-moderate and mostly self-inflicted. The package itself is small and stable; the recurring cost is flag debt — every flag is a branch in the code that lives forever unless someone owns its removal. The realistic ongoing burden is the convention, not the dependency: a named owner and expiry per flag, a periodic sweep for stale toggles, and an authorization process for production flips. Budget for that discipline explicitly, because the alternative is a features table with two hundred rows, half of which nobody can safely delete.
Support. Single maintainer, MIT, small enough to read in an afternoon and fork if abandoned. The practical support model is "we can own this if we have to" — which is acceptable for a component this size and unacceptable for anything larger. Escalation paths are limited, so any production incident rooted in the package will be resolved by our own engineers reading the source, not by a vendor. That is a legitimate trade for a utility; it is not a trade you make for a platform.
Scaling. Fine at the flag volumes this package targets — tens of flags, boolean semantics, evaluations that hit cache rather than the database. It will not scale into per-user targeting, per-request database lookups at high concurrency, or cross-service consistency, and it should not be asked to. The scaling question to answer up front is consumption topology: how many services, how many evaluation points, and whether config caching and queue workers can hold stale state across a flip. If any of those answers trend toward "many," the right move is to stop extending this package and open a formal spike for a dedicated flag service.
Failure modes. Four to plan for. First, missing flag reads as off — a silent no-op that turns staging into a placebo; the deploy-time seeder is the mitigation, and a startup assertion in non-production environments is a cheap hardening step. Second, stale cache after a toggle — the flip looks applied but traffic still sees the old state; document the cache-clear step as part of the flip runbook and verify under the same caching configuration as production. Third, flip-before-migrate — the classic 500 on destructive launches; enforce the fixed order (schema, then flag, then removal) in the release checklist. Fourth, unowned flag accumulation — not an outage, but a slow erosion of the codebase that eventually makes "is this feature on?" unanswerable without archaeology.
Ramp-up. Low for engineers — the API is a facade call and a Blade directive, roughly an hour to first flag. Higher for the organization, because the package supplies storage and lookup but no governance: naming conventions, ownership, authorization on flips, and cleanup cadence are all things we define. The realistic ramp is one engineer-day to integrate and document, plus one pilot release to prove the conventions, plus an ongoing trickle of process discipline. Plan for the process cost explicitly — it is small, but it is the entire difference between a flag system and a flag graveyard.
How can I help you explore Laravel packages today?