Skip to main content

What is Laravel Pennant?

Laravel Pennant is a simple, lightweight feature flag package. Feature flags let you incrementally roll out new application features with confidence, run A/B tests on new interface designs, complement trunk-based development strategies, and more.

Feature Flag Flow

Feature flags decouple deployment from release — deploy code to production without exposing it to users until you’re ready.

Installation

1

Install the package

Install Pennant via Composer:
2

Publish configuration and migrations

Publish Pennant’s config file and database migrations:
3

Run migrations

Create the features table used by the database driver:

Configuration

After publishing, the config file is located at config/pennant.php. You can set the default storage driver here.

Defining Features

Closure-based Features

Define features using the Feature facade’s define method, typically in a service provider’s boot method. The closure receives the feature’s “scope” — usually the authenticated user.
The feature logic above:
  • Always ON for internal team members
  • Always OFF for high-traffic customers
  • Randomly enabled for 1% of everyone else
The first time a feature is checked for a given scope, the result is stored. Subsequent checks retrieve the stored value.
If a feature definition only returns a Lottery, you can omit the closure entirely:

Class-based Features

Class-based features do not need to be registered in a service provider. Generate one with the Artisan command:
The class is placed in app/Features. Implement the resolve method:

Customizing the Stored Feature Name

By default, Pennant stores the fully qualified class name. Use the Name attribute to decouple the stored name from the class name:

Intercepting Feature Checks (before method)

Class-based features may define a before method that runs in-memory before the stored value is retrieved. If a non-null value is returned, it overrides the stored value for the duration of the request.
The before method is useful for emergency kill switches or scheduling a global rollout on a specific date.

Checking Features

Feature::active() / Feature::inactive()

Use the active method to check whether a feature is active. By default, the currently authenticated user is used as the scope.
For class-based features, pass the class name:
Additional helper methods are available:

Conditional Execution (when / unless)

Use when to fluently execute a closure when a feature is active:
unless is the inverse — the first closure runs when the feature is inactive:

The HasFeatures Trait

Add HasFeatures to your User model to check features directly from the model:

Blade Directive

Pennant provides @feature and @featureany Blade directives:

Middleware

Use EnsureFeaturesAreActive to require features to be active before a route can be accessed. If any listed feature is inactive, a 400 Bad Request response is returned.
Customize the response using whenInactive:

In-Memory Cache

Pennant caches resolved feature values in memory for the duration of a request. The same feature will not trigger additional database queries when checked multiple times. Manually flush the cache with:

Scope

Specifying the Scope

Use the for method to check a feature against a specific scope:
Example: rolling out a billing feature to teams based on their age:

Default Scope

Customize the default scope using Feature::resolveScopeUsing:
Now calling Feature::active('billing-v2') automatically uses the team scope.

Nullable Scope

If the scope is null (unauthenticated routes, Artisan commands, queued jobs) and the feature definition does not handle null, Pennant returns false. Use nullable types to handle this:

Rich Feature Values

Features can return values other than booleans. This is useful for A/B testing:
Retrieve the value using the value method:
Use Blade to render content based on the value:
When using rich values, a feature is considered “active” when it has any value other than false.
The rich value is passed to the first closure of when:

Retrieving Multiple Features

Use values to retrieve multiple features at once:
Use all to retrieve all defined features:
To include class-based features in all results, call discover in a service provider:
This registers all feature classes in app/Features.

Eager Loading

Avoid N+1 queries when checking features in a loop by using load:
Load only values that haven’t been loaded yet:

Updating Values

Manual Updates

Toggle a feature on or off using activate and deactivate:
To forget a stored value so the feature resolves from its definition again:

Bulk Updates

Apply a value to all scopes at once:

Purging Features

Remove all stored values for a feature using purge:
Use the Artisan command to purge from the command line:

Testing

Re-defining Features

The easiest way to control feature values in tests is to re-define the feature:
tab=Pest
tab=PHPUnit
Class-based features work the same way:
tab=Pest
tab=PHPUnit

Test Store Configuration

Configure the Pennant store for testing in phpunit.xml:

Custom Drivers

If the built-in drivers don’t meet your needs, implement the Laravel\Pennant\Contracts\Driver interface:
Register it using Feature::extend in a service provider:
Then set the driver in config/pennant.php:

Summary

Next Steps

Error Handling

Learn how Laravel handles and reports exceptions.

Laravel Pulse

Add a performance monitoring dashboard to your application.
Last modified on May 19, 2026