Feature flags are not optional once you've shipped a broken feature at 11pm and spent two hours rolling back a deployment.
After that, they become non-negotiable.
The common response: sign up for LaunchDarkly. $300-$600/month. Their SDK in every feature check. Their user model separate from yours. Your user data sent to their cloud.
Laravel Pennant does everything LaunchDarkly does. Costs $0. Integrates with your Eloquent models. Stores in your database.
Here's the complete toolkit 👇
🎯 Gradual rollouts — 10% today, 50% tomorrow
Feature::define('new-checkout', Lottery::odds(1, 10));
Key behaviour: once resolved for a user, the result is stored. A user who gets true at 10% always gets true. No flickering.
Increase the rollout: php artisan pennant:purge new-checkout, update the odds, re-resolve.
—
💰 Rich values for A/B testing
Feature::define('checkout-button-color', fn(User
$u) =>
$u->id % 2 === 0 ? 'blue' : 'green'
);
$color = Feature::value('checkout-button-color');
Not just true/false — any serialisable value. Variant tracking, configuration per plan, rate limits per tier.
—
🛡️ Kill switches — the most important use case
Feature::define('payment-processing', fn() => true);
// At 3am when something breaks:
Feature::deactivateForEveryone('payment-processing');
// No deployment. No git revert. 10 seconds.
Feature::activateForEveryone('payment-processing');
// Back online when the fix is ready.
—
🏢 Per-tenant flags for multi-tenancy
Feature::define('advanced-reporting', fn(Tenant
$t) =>
$t->plan === 'enterprise' ||
$t->beta_features_enabled
);
Feature::for($tenant)->active('advanced-reporting');
Scope to any model — User, Team, Tenant, Subscription. Pennant doesn't assume the scope.
—
🔔 The UnknownFeatureResolved event
Catches stale flag references after you remove a flag definition:
Event::listen(UnknownFeatureResolved::class, function ($event) {
if (app()->isLocal()) throw new RuntimeException("Unknown flag: {$event->feature}");
Log::warning('Stale feature flag checked', ['feature' =>
$event->feature]);
});
Remove the definition, leave the Feature::active() call by mistake — you'll know immediately.
The workflow that makes shipping safe:
1. Define flag (default false)
2. Enable for your team
3. Enable for beta testers
4. Lottery::odds(1, 10) → 10% rollout
5. Purge increase to 25%, 50%, 100%
6. Feature::activateForEveryone()
7. Remove the flag and the conditional code
Every feature that goes to production without a flag requires a deployment to roll back. Every feature behind a flag requires one Artisan command.
📖 Full guide — all definition approaches, Blade directives, middleware gates, kill switches, per-tenant scoping, A/B testing with rich values, Pennant events, testing with Feature::fake(), the rollout workflow, and the honest Pennant vs LaunchDarkly comparison.
#Laravel #PHP #FeatureFlags #LaravelPennant #BackendDevelopment #PHPDevelopment #WebDevelopment #SoftwareEngineering #LaravelPHP #BackendEngineering #ContinuousDelivery #DevOps #Programming #TechCommunity #SoftwareDevelopment #CodeQuality #SoftwareCraft #OpenSource #LaravelTips #ABTesting medium.com/p/laravel-pennant…