Checking features
Values
Features::value('projects', $team); // 50, null (unlimited) or 0; booleans return bool
Features::active('api_access', $team); // limits are active unless 0
Features::limit('projects', $team); // ?int
Features::used('projects', $team); // from the usage resolver
Features::remaining('projects', $team); // ?int
Features::canUse('projects', $team, 2);
Features::source('projects', $team); // override | plan | global | default
Features::for($team)->snapshot(); // everything, formatted, with usage
Omit the scope and the current Filament tenant is used. Every method is also on Features::for($team) and on the HasFeatures trait.
Enforcing
public function store(Team $team): void
{
Features::assertCanUse('projects', $team); // FeatureLimitReached / FeatureInactive
$team->projects()->create([...]);
}
FeatureLimitReached carries feature, limit and used; FeatureLimitExceeded is dispatched at the same time so you can nudge the user to upgrade.
Gating
Routes
Route::get('/tokens', ...)->middleware('feature:api_access'); // 403
Route::get('/reports', ...)->middleware('feature:reports,redirect'); // redirect to the upgrade URL
Pages and resources
use Packstub\Features\Filament\Concerns\RequiresFeature;
class ReportsPage extends Page
{
use RequiresFeature;
protected static string $feature = 'reports'; // hidden from navigation and 403 on direct access
}
Actions and form components
Action::make('export')->requiresFeature('api_access');
TextInput::make('webhook_url')->requiresFeature('api_access');
Section::make('Integrations')->requiresFeature('api_access', fn () => $this->record->team);
Blade
@feature('reports')
<a href="{{ route('reports') }}">Reports</a>
@else
<x-packstub-features::locked feature="reports">Reports are part of the Pro plan.</x-packstub-features::locked>
@endfeature
The plan page
Packstub\Features\Filament\Pages\Plan (/plan) shows every feature by group, usage bars for limits with a resolver, "custom" markers for overrides, and an Upgrade button when an upgrade URL exists.