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

Pennant Laravel Package

laravel/pennant

Laravel Pennant is a simple, lightweight feature flag library for Laravel. Define flags in a service provider and check them with Feature::active() to roll out features gradually to users, segments, or percentages of traffic.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

  • Pennant is a first-party Laravel package that binds into the service container via a service provider and is consumed through the Feature facade/contract. It slots into the existing provider + facade conventions rather than introducing a parallel configuration or runtime model.
  • The design is deliberately thin: flags are defined in application code as closures bound to a Feature name, evaluated against a scope (a model instance, or the global scope added in 1.26.0). Drivers (database, array, null, and custom) supply persistence and resolution.
  • It covers the full request lifecycle a TPM would expect from an in-app flag system: middleware (EnsureFeaturesAreActive, including all checks added in 1.5.0), Blade directives (@feature, @featureany), events for feature updates, a pennant:purge command with exclusions, before hooks (1.9.0/1.14.0), a nameMap decorator for renaming, PHP enum support, and external-driver hooks for retrieving defined features per scope.

Integration feasibility

  • composer require laravel/pennant plus a published migration for the database driver. No external SaaS, API keys, network calls, or vendor onboarding — this is the lowest-friction integration class available.
  • Testing story is clean: the array driver means flags do not require a database in the test suite, which keeps CI parity intact.
  • The custom-driver contract is the strategically important affordance: it lets Pennant act as a facade over an existing flag provider (LaunchDarkly, Unleash, ConfigCat) if one is already in place, avoiding a rip-and-replace.

Technical risk

  • Primary risk is governance, not technology. Pennant has no admin console and no runtime targeting UI: flag definitions and targeting rules are code and ship with a deploy. Per-scope state can be toggled at runtime (activate/deactivate/forget), but "flip a rule for 10% of EU users without a release" is not a native capability. If product stakeholders expect a LaunchDarkly-style dashboard, scope expectations now.
  • Rollout determinism: percentage rollouts must be written inside the closure. A naive Lottery-based closure re-rolls per request, producing a flickering experience unless the result is persisted per scope or derived from a stable hash. This is the most common implementation defect and needs an internal pattern, not ad-hoc code.
  • Flag debt is unmanaged by design. Nothing expires a flag. pennant:purge cleans stored values, not dead definitions or dead code branches. Stale flags are the dominant long-term cost.
  • No cross-language SDK. Pennant is PHP/Laravel only. If flags must be honoured by a mobile client, a Node BFF, or a Go service, it cannot be the single source of truth.
  • Data hygiene: flag evaluation is cached, so multi-node deployments depend on a shared cache store; and renaming features or changing scope serialization (morph map) can silently orphan stored values.

Key questions

  1. Do we need to change targeting for a user segment without a deploy? If yes, Pennant is the wrong primary tool (or must sit behind a custom driver).
  2. Which scope identifies a user — User model, tenant, or global? Multi-tenancy changes the row-cardinality and purge strategy materially.
  3. What is the expected flag-evaluation volume per request, and is a shared cache (Redis) already provisioned?
  4. Do any non-PHP surfaces need the same decision? If so, who owns the second implementation?
  5. What is the flag lifecycle policy — owner, expiry date, removal ticket — and who enforces it?
  6. Is an audit trail required for who enabled what and when (events exist; is a listener in place)?
  7. Are we standardising on ^1.x and tracking Laravel's release cadence, or pinning?

Integration Approach

Stack fit

  • Requires a Laravel application on a currently supported major (changelog shows Laravel 11, 12, and 13 support; PHP 8.4 and 8.5 compatibility work is present). MIT licensed, released on the same cadence and by the same maintainer group as the framework — this is a "boring dependency," which is the right classification for a flag layer.
  • Database driver targets the configured connection (corrected in 1.16.0), with configurable table name and timestamp columns, and morph-map support for scope serialization (1.4.0) — so it fits apps that already use morph maps rather than forcing a schema convention.
  • Works with long-running workers (Octane/queue) provided cache flushing is wired: driver-level cache-flush opt-in exists as of 1.15.0.

Migration path

  • From env/config booleans: define one Feature per existing boolean, wrap the existing check site in Feature::active(...), and delete the config key. Call sites change once; behaviour is unchanged on day one.
  • From an external provider: implement a custom driver that delegates to the incumbent, keeping Feature::active() as the single call-site API. This decouples the eventual cutover from the code change and is the recommended path if a provider contract is in force.
  • From nothing: start with the array driver in tests and a single internal-only flag, then publish the migration and move to the database driver.

Compatibility

  • Watch items rather than blockers: cache store consistency across nodes, scope serialization stability if morph maps are introduced later, and feature renames (mitigated by the nameMap decorator, 1.18.0). PHP 8.4/8.5 and Laravel 13 support mean the package is not a constraint on the platform upgrade roadmap.

Sequencing

  1. Add the dependency, register a conventions doc (naming, owner, expiry, default state), and define the first flag behind the array driver in tests.
  2. Publish and review the migration; confirm connection, table name, and index strategy; move to the database driver.
  3. Instrument a listener on the feature-update events so every activation is attributable.
  4. Adopt middleware and Blade directives for route/UI gating rather than scattering if statements in controllers.
  5. Establish a recurring pennant:purge schedule with an exclusion list for long-lived flags.
  6. Only then evaluate rollout-pattern helpers (deterministic hashing) and any custom driver for external providers.

Operational Impact

Maintenance

  • Low code surface, active first-party maintenance, Dependabot and CI hardening in recent releases, and a documented security policy. Version pin to ^1.x; releases are frequent but small and mostly additive, so upgrade cost is near zero.
  • The real maintenance load is flag hygiene, not the package. Budget for a quarterly review of active flags against their expiry dates and a corresponding dead-code sweep.
  • Caveat on the data: Dependents: 0 is a weak third-party adoption signal in this dataset (plausibly a reporting artifact for a first-party package). Treat the confidence case as "maintained by the Laravel core team," not "battle-tested across hundreds of public dependents."

Support

  • Documentation lives on laravel.com (not the repo README), with changelog and releases present but no commercial SLA. Support is community + internal ownership. Nominate an internal owner for the flag catalog and the conventions doc; that person, not the vendor, is the escalation path.
  • Onboarding material for new engineers must cover: how to define a flag, how to scope it, and how to remove it.

Scaling

  • Database driver stores one row per feature/scope pair. Global and per-tenant scopes stay small; per-user scopes on a high-traffic app grow the table linearly with active users.
  • Read path is cache-backed; writes occur on activation. Ensure a shared cache store fronting the DB driver for multi-node deployments, and index the feature/scope columns.
  • Concurrency: unique-constraint collision handling was explicitly improved for high-traffic apps (1.8.0/1.8.1) and bulk writes were optimised (1.18.5), but expect occasional write retries under burst activation — acceptable for admin-triggered toggles, not for write-heavy flows.
  • loadAll (1.10.0) and per-scope bulk retrieval keep page rendering from issuing N flag queries.

Failure modes

  • Cache unavailable: evaluation falls back to DB reads — degraded latency, not incorrect decisions. Acceptable.
  • Database unavailable: flag evaluation can throw on the request path. Decide and document a policy — default-inactive with a fail-safe wrapper, or a before hook that short-circuits — before this happens in production, not during an incident.
  • Non-deterministic rollouts: lottery-in-closure flags cause users to flip between experiences per request. Mandate a persisted or hash-derived pattern.
  • Stale flags: shipped code paths guarded by flags nobody remembers; the biggest silent risk to code health and a source of confusing behaviour when a flag is finally removed.
  • Rename/rescope drift: changing a feature name or scope serialization orphans stored values, silently reverting cohorts to defaults. Use nameMap and treat renames as migrations.
  • Deploy coupling: a targeting change requires a release. If product expects otherwise, this surfaces as a process failure under pressure, not a bug.

Ramp-up

  • For a Laravel-fluent engineer: half a day to first flag in production, one to two days to be fully productive including drivers, middleware, and testing patterns. Near-zero framework-specific learning curve.
  • The organisational ramp-up is longer than the technical one: agreeing flag naming, scope model, default-state policy, expiry discipline, and the rollout-determinism helper. Sequence that work in week one, or the first ten flags will each invent their own convention.
  • Recommended adoption gate: run one release cycle with 2–3 flags and measure whether the expiry/cleanup step actually happens. If it does not, the constraint is process, and adding more flags will not be safe regardless of the package's quality.
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.
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo
bernskioldmedia/laravel-harvest
bernskiold/laravel-data-scrubber
bernskiold/laravel-currency-converter
bernskiold/laravel-campaign-monitor
bernskioldmedia/laravel-autotranslate
siberfx/telegram-bot
roy-sandip/laravel-view-extended
gregsphotography/site-builder-core
roy-sandip/laravel-view
anodyne/blade-fluentui-icons
webx-ui/module-catalog-collections