Deploying code used to feel like a high-stakes gamble. You pushed your changes and hoped for the best. If something broke, you scrambled to revert the entire deployment. That era is over. Laravel Pennant introduces a better way to manage change.
Pennant is a first-party feature flag package. It decouples deployment from release. You can ship code to production without making it visible to users. You toggle features at runtime. It is lightweight, expressive, and built for the modern Laravel ecosystem.
The Power of Decoupling: Deploy vs. Release
Deployment is a technical event. You move code from your machine to a server. Release is a business event. You make that code available to your customers. Traditional workflows conflate these two. Pennant separates them.
This separation reduces risk. You can deploy a "half-finished" feature behind a flag. Your team can test it in production environments like Laravel Cloud without affecting real users. If a bug appears, you flip a switch. No rollback required. No frantic git revert. Just a simple database update.
Defining Features: Closures and Classes
Pennant offers two ways to define features. Simple flags live in your AppServiceProvider. You use the Feature::define method. It takes a name and a closure.
use Laravel\Pennant\Feature;
use App\Models\User;
Feature::define('new-dashboard', fn (User $user) => $user->isBetaTester());
This logic is scoped to the user. Pennant automatically resolves the currently authenticated user. If the closure returns true, the feature is active.
For more complex logic, use class-based features. These encapsulate your rules into dedicated files. You can generate them using a simple Artisan command: php artisan pennant:feature NewApi.
namespace App\Features;
use App\Models\User;
class NewApi
{
public function resolve(User $user): bool
{
return $user->plan === 'premium' && $user->created_at->year < 2026;
}
}
Class-based features keep your service providers clean. They are easier to test. They allow you to organize feature logic alongside your domain models.

Scoping and Targeting: Who Sees What?
Not all flags are global. Most features target specific segments. Pennant handles this through "scopes." By default, features scope to the authenticated user. You can change this easily.
// Check for a specific team
Feature::for($team)->active('billing-v2');
// Check for a specific tenant
Feature::for($tenant)->active('custom-theme');
This flexibility allows for powerful rollout strategies. You can enable features for internal employees first. Then move to a small group of beta testers. Finally, you roll it out to the entire world.
A/B Testing and Gradual Rollouts
Pennant excels at experimentation. You can use it for A/B testing without third-party services. Define a feature that returns a random value.
Feature::define('cta-color', function (User $user) {
return lottery(1, 2) ? 'red' : 'blue';
});
The lottery helper is a clean way to split traffic. Once a user is assigned a value, Pennant persists it. Their experience remains consistent across sessions.
Gradual rollouts work similarly. You can slowly increase the percentage of users seeing a new feature. Start at 10%. Check your logs in Nightwatch. If the error rates stay low, bump it to 50%. This "canary release" pattern is the gold standard for high-traffic applications.
Integrating with the Frontend
Feature flags shouldn't just live in your controllers. Pennant integrates deeply with Blade templates. You don't need messy if statements.
@feature('new-dashboard')
<x-new-dashboard-layout />
@elsefeature
<x-legacy-dashboard />
@endfeature
This keeps your UI logic readable. It also prevents leaking "unreleased" UI elements to the general public. You can even guard entire routes using the provided middleware.
Route::get('/v2/api', [ApiController::class, 'index'])
->middleware('ensure.features:new-api');
If the feature is inactive, the user receives a 400 Bad Request. You can customize this behavior to redirect users or show a specific "coming soon" page.

Persistence: Where Flags Live
Pennant supports multiple storage drivers. For local development, the array driver is perfect. It resets every request. For production, the database driver is essential.
The database driver stores the resolved state of each feature for every user. This ensures that once a user sees the "New Dashboard," they keep seeing it. It also allows you to manage flags globally via the database.
Sometimes you need a "kill switch." A feature might be causing performance issues. You can deactivate it globally with a single Artisan command:
php artisan pennant:deactivate new-dashboard
This command updates the database immediately. All users lose access to the feature. You can fix the bug in your own time without the pressure of a broken production site.
Best Practices for Pennant
Flags are powerful, but they add complexity. Follow these guidelines to keep your codebase healthy.
Descriptive Naming: Colons and Clarity
Use clear names. test-feature is useless. billing:stripe-v3-migration is perfect. Good names describe the "what" and the "why."
Short-Lived Flags
Do not let flags live forever. A feature flag is a debt. Once a feature is 100% rolled out, delete the flag. Remove the branching logic. Delete the feature class. Keeping old flags leads to "dead code" and confusion.
Testing Both States
Always write tests for both the "on" and "off" states of your features. Pennant makes this easy with its testing helpers.
Feature::activate('new-api');
$this->get('/api/data')->assertStatus(200);
Feature::deactivate('new-api');
$this->get('/api/data')->assertStatus(400);
Categorical Subheaders for Technical Changes
When managing large sets of flags, group them in your config/pennant.php file using tags.
- UI Changes: Flags affecting the visual interface.
- API Versions: Logic for different API endpoints.
- Infrastructure: Toggles for background processes or database drivers.
Forward-Looking Releases
The Laravel ecosystem is designed for speed. Tools like Forge and Vapor handle the "where" of your deployment. Pennant handles the "when."
We are moving toward a world where every change is a toggle. This allows developers to work on large features in small, manageable chunks. You can merge your code daily without fear. You are always ready to ship.

Join the Conversation
Laravel Pennant is more than a package. It is a philosophy of safer shipping. It empowers small teams to act like giant enterprises. It gives you the confidence to innovate without the fear of failure.
We'd love to hear how you're using Pennant in your projects. Are you doing complex A/B testing? Are you using it for plan-based access control? Every corner of the community has a unique story. Your story belongs here.