Handling null scopes in Laravel Pennant feature checks involves understanding how Pennant deals with feature scopes, especially in contexts where there is no authenticated user, such as Artisan commands, queued jobs, or unauthenticated routes.
What is a Null Scope in Laravel Pennant?
A null scope occurs when you check a feature without an authenticated user or an explicit scope. By default, Pennant uses the currently authenticated user as the scope for feature checks. However, in non-HTTP contexts or unauthenticated routes, the scope defaults to `null` because there is no user available[1].
Default Behavior with Null Scopes
- If you pass a `null` scope to a feature check, and the feature's definition does not support `null` (i.e., the feature callback or class does not accept a nullable type or `null` in a union type), Pennant will automatically return `false` for that feature.
- This means the feature will be considered inactive in the absence of a valid scope unless you explicitly handle `null` in your feature definition[1].
How to Handle Null Scopes Properly
1. Make the Feature Scope Nullable
You should define your feature to accept a nullable scope type, for example, `User|null`. Then handle the `null` case explicitly inside the feature definition:
php
use App\Models\User;
use Illuminate\Support\Lottery;
use Laravel\Pennant\Feature;
Feature::define('new-api', fn (User|null $user) => match (true) {
$user === null => true, // or false, depending on your default
$user->isInternalTeamMember() => true,
$user->isHighTrafficCustomer() => false,
default => Lottery::odds(1 / 100),
});
This ensures the feature logic runs even if the scope is `null` and you can set a global default behavior for unauthenticated or background contexts[1].
2. Explicitly Pass `null` as the Scope When Checking
When you want to check a feature globally (i.e., no user or specific scope), you can explicitly pass `null` as the scope:
php
Feature::for(null)->active('new-feature');
This will trigger the feature's nullable scope logic and allow you to define global feature flags that apply to everyone, regardless of user context[2].
3. Set a Default Scope Resolver
If you want Pennant to automatically use `null` as the default scope whenever no scope is provided (e.g., in Artisan commands or jobs), you can configure this in a service provider, such as `AppServiceProvider`:
php
use Laravel\Pennant\Feature;
public function boot(): void
{
Feature::resolveScopeUsing(fn ($driver) => null);
}
This makes `null` the default scope globally, so you don't have to specify it manually every time you check a feature outside an HTTP request with an authenticated user[2].
4. Handle Unexpected Null Scope Events (Optional)
Pennant dispatches an event `UnexpectedNullScopeEncountered` when a `null` scope is passed to a feature that does not support it. By default, this returns `false` gracefully, but you can listen to this event to handle it differently (e.g., throw an error):
php
use Laravel\Pennant\Events\UnexpectedNullScopeEncountered;
use Illuminate\Support\Facades\Event;
public function boot(): void
{
Event::listen(UnexpectedNullScopeEncountered::class, fn () => abort(500));
}
This is useful for debugging or enforcing strict scope handling[1].
Summary
- Null scopes happen in unauthenticated or non-HTTP contexts.
- Define your features with nullable scope types (`User|null`) to handle null gracefully.
- Explicitly pass `null` as the scope for global feature checks.
- Optionally set a default scope resolver to `null` in your service provider.
- Listen to `UnexpectedNullScopeEncountered` event if you want to customize null scope handling.
By following these steps, you ensure your Laravel Pennant feature flags work consistently across all contexts, including those without an authenticated user[1][2].
Citations:
[1] https://laravel.com/docs/11.x/pennant
[2] https://elliotderhay.com/blog/how-to-make-laravel-pennant-features-that-apply-globally
[3] https://github.com/laravel/pennant/issues/57
[4] https://www.honeybadger.io/blog/a-guide-to-feature-flags-in-laravel/
[5] https://hackernoon.com/how-to-manage-feature-flags-with-laravel-pennant
[6] https://laravel-news.com/feature-flags-with-laravel-pennant
[7] https://laravel.com/docs/12.x/pennant
[8] https://laravel.com/docs/11.x/validation