Packstub.

Configuration

Key Default Purpose
scope_model App\Models\Team the tenant model (admin scope picker, features:list)
features [] feature definitions (label, type boolean/limit, default, description, unit, group)
plans [] plan definitions (label, description, price, features)
default_plan free plan when the resolver returns nothing
overrides_table feature_overrides
connection null central connection in database-per-tenant apps
cache.store / cache.ttl null / 300 cross-request override cache
upgrade_url null where locked features send people

The plugin API

FeaturesPlugin::make()
    ->features([...])
    ->plans([...], default: 'free')
    ->planUsing(fn (?Model $scope) => ...)      // string, Plan or null
    ->usage('projects', fn (?Model $scope) => ...)
    ->upgradeUrl(fn (?Model $scope) => ...)     // or a string
    ->planPage(MyPlanPage::class)               // null to skip
    ->adminPages()                              // FeatureFlags + FeatureOverrides
    ->navigationGroup('Account')->navigationSort(90)
    ->config(['cache.store' => 'redis']);

Commands

  • features:list {scope?} — features, defaults, plan values and the resolved value for a scope.
  • features:prune — delete expired overrides.

With Filament Teams

TeamsPlugin::make()->seatLimit(fn (Team $team): ?int => Features::limit('seats', $team));

With Filament Tenancy

Set scope_model to the Tenant model and connection to the central connection; overrides use string scope ids, so Tenancy's string keys work as they are.