Going Further

Feature Flags

Pennant-based runtime toggles for whole capabilities — features, domains, or surface areas that flip ON or OFF for a deploy, tenant, or user.

A feature flag turns a whole capability on or off. It's a runtime switch you can flip without a deploy: per environment, per tenant, eventually per user. The project uses Laravel Pennant for this.

Feature flags answer "is this capability available?". For "which build of the product is this?" (white-label / cosmetic differences), use Project Flavor instead.

The rule: reach for a flag when the boundary is a whole capability — a complete feature, domain, or surface area is either ON or OFF. Examples: the entire winch_branches domain (its routes, its menu groups, the internal-invoices group that depends on it). The flag is a binary capability switch.

The shape

use Domain\Core\Enums\FeatureFlag;
use Laravel\Pennant\Feature;

if (Feature::active(FeatureFlag::WinchBranches->value)) {
    // capability is on
}
@feature('winch_branches')
    <a href="{{ route('cp.winch_branches.index') }}">Branches</a>
@endfeature
// route file
Route::middleware('feature:winch_branches')->group(function () {
    Route::resource('winch_branches', WinchBranchesController::class);
});

The flag is checked at request time (middleware), in Blade (@feature), in PHP. An inactive flag returns 404 from the middleware (configured in App\Providers\FeatureServiceProvider).

Adding a new flag

  1. Add a case to Domain\Core\Enums\FeatureFlag.
  2. Add a default in config/features.php:
    'my_flag' => env('FEATURE_MY_FLAG', false),
    
  3. Optionally, create a class extending Domain\Core\Abstracts\Feature for typed enabled() / disabled() and feature-specific helpers — WinchBranchesFeature is the canonical example.
  4. Routes — gate with middleware:
    Route::middleware('feature:my_flag')->group(function () {
        Route::resource('things', ThingsController::class);
    });
    
  5. Blade:
    @feature('my_flag')
        ...
    @endfeature
    
  6. Menu — add the flag to group_features or route_features in config/cp-menu.php so the sidebar prunes the link when the flag is off.
  7. Operator toggles via the FEATURE_MY_FLAG env var.
Steps 4 and 6 are independent. The middleware 404s the request; it does not prune the sidebar. If you only do one, users see a menu link that 404s when they click it.

Anti-patterns

  • Adding a Pennant flag to hide a single field. Use Project Flavor — a flag has overhead (config, env, registration, middleware support) that doesn't pay off for one form field.
  • Auto-deriving flag values from ProjectFlavor. They stay independent on purpose: a saas deploy may still need to flip a flag for unrelated reasons (e.g., a new feature being soft-launched).
  • Forgetting menu pruning. Adding feature: middleware without a matching group_features / route_features entry leaves a sidebar link that 404s when clicked.

Reference files

  • src/Domain/Core/Enums/FeatureFlag.php — the flag enum (one case per flag).
  • src/Domain/Core/Abstracts/Feature.php — base class for typed feature classes.
  • config/features.php — defaults read from env vars.
  • config/cp-menu.phpgroup_features / route_features maps.
  • app/Packages/Menus/CpMenu.php — menu builder that consumes the maps.
  • App\Providers\FeatureServiceProvider — middleware 404 behaviour.
  • Project Flavor — the cosmetic / build-time counterpart, plus a comparison table.
  • Deployment — how flag rollouts propagate to production.
  • Domain boundaries — why a flag goes around a whole domain, not inside one.