Lập trình · 20/09/2026

Laravel Pennant: Safe Feature Flags and Gradual Rollouts

Deploying code and releasing a feature do not have to happen at the same time. A feature flag lets a team ship dormant code, enable it for internal users, expand it to stable cohorts, and disable it quickly when metrics deteriorate. Laravel Pennant provides this workflow through a small API, explicit scopes, and persistent drivers.

Laravel Pennant: Triển khai feature flag và rollout tính năng an toàn

Deploying code and releasing a feature do not have to happen at the same time. A feature flag lets a team ship dormant code, enable it for internal users, expand it to stable cohorts, and disable it quickly when metrics deteriorate. Laravel Pennant provides this workflow through a small API, explicit scopes, and persistent drivers.

This guide focuses on disciplined use: defining flags, choosing scopes, rolling out, implementing a kill switch, testing both paths, and removing a flag after it has completed its job.

What problems do feature flags solve?

ScenarioHow a flag helps
Canary releaseEnable a change for staff or a small cohort first.
Trunk-based developmentMerge incomplete behavior while keeping the new path disabled.
A/B testingResolve a variant value rather than a simple boolean.
Risk controlDisable a new path without rolling back the entire deployment.

A feature flag is not authorization. Authorization answers what a user is permitted to do; a flag selects which experience or implementation is currently being rolled out.

Install Laravel Pennant

composer require laravel/pennant
php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"
php artisan migrate

The database driver persists resolved values. The array driver is useful for tests and non-persistent scenarios.

Define an initial flag

use App\Models\User;
use Illuminate\Support\Lottery;
use Laravel\Pennant\Feature;

Feature::define('checkout-v2', fn (User $user) => match (true) {
    $user->isInternalTeamMember() => true,
    $user->hasSupportHold() => false,
    default => Lottery::odds(1, 20),
});

The first check resolves and stores a result for that scope. The same user therefore keeps a stable experience instead of moving randomly between paths on each request.

Choose the rollout scope deliberately

User scope is common, but a SaaS application may need to roll out by team or organization so every member receives the same behavior.

if (Feature::for($user->team)->active('billing-v2')) {
    return redirect()->route('billing.v2');
}

Choose the scope before rollout. Switching from users to teams midway creates a new set of stored resolutions and makes behavior harder to explain.

Use class-based features for important logic

final class CheckoutV2
{
    public function before(User $user): mixed
    {
        if (config('features.checkout_v2_disabled')) {
            return $user->isInternalTeamMember();
        }

        return null;
    }

    public function resolve(User $user): bool
    {
        return $user->created_at->isAfter(now()->subMonths(3));
    }
}

The before() method runs ahead of stored values and can act as a kill switch. An incident can disable the feature broadly without deleting prior assignments, allowing the same cohort to return after a fix.

Check the flag at the main behavior boundary

return Feature::for($user)->active('checkout-v2')
    ? $newCheckout->handle($cart)
    : $legacyCheckout->handle($cart);

Keeping the decision near the primary branch makes both paths easier to understand and remove. Avoid scattering the same flag across controllers, views, jobs, and services when a coordinating layer can make one decision.

Blade offers the @feature directive, while Pennant middleware can protect an entire route behind active features.

Use rich values for controlled variants

Feature::define('checkout-button', fn (User $user) =>
    $user->country_code === 'VN' ? 'compact' : 'standard'
);

$variant = Feature::value('checkout-button');

Rich values suit interface variants and rollout parameters. Keep the value set small and stable, and record it with analytics. A feature store should not become an arbitrary business-configuration database.

Avoid N+1 checks across many scopes

Feature::for($users)->load(['notifications-beta']);

foreach ($users as $user) {
    if (Feature::for($user)->active('notifications-beta')) {
        // ...
    }
}

Pennant caches checks within one request, but iterating over many users can still trigger many database queries. Eager load the values needed by batch workflows.

Test both paths

Feature::define('checkout-v2', true);

$this->actingAs($user)
    ->post('/checkout', $payload)
    ->assertRedirect('/orders/success');

Feature::define('checkout-v2', false);

Cover both enabled and disabled behavior until the legacy path is removed. Redefine feature results in tests so they do not depend on random cohort assignment.

Plan the rollout sequence

  1. Deploy schema and code that support both old and new paths.
  2. Enable internal users and monitor errors, latency, and business metrics.
  3. Enable a small, stable cohort.
  4. Increase exposure when predefined thresholds remain healthy.
  5. Enable everyone and keep observing for a safety period.
  6. Remove the legacy path, flag definition, stored values, and temporary dashboards.

Database migrations must remain backward compatible during the rollout. Disabling a flag cannot recover the legacy path if a deployment already removed a column it requires.

Clean up completed flags

php artisan pennant:purge checkout-v2

Every flag should have an owner, purpose, creation date, and removal condition. Long-lived flags multiply branches and tests. A feature flag is a transition mechanism, not a default permanent state.

Pennant checklist

  • Names describe behavior instead of a short-lived ticket number.
  • The scope matches the rollout unit: user, team, or global.
  • High-risk changes have a kill switch.
  • Metrics are segmented by variant and include stop thresholds.
  • Both enabled and disabled paths are tested.
  • The schema supports both paths during transition.
  • An owner and flag-removal plan are recorded.

References

Discussion

Comments 0

Sign in to comment

You need an account to join the discussion and reply to other readers.

Sign inRegister

No comments yet. Be the first to share your thoughts.