We have all been there. You are working on a killer new feature (like the AI Rewriter we built yesterday), but it is not quite ready for everyone.
Do you delay the deployment? Do you keep it in a separate git branch for weeks, creating a merge conflict nightmare?
No. You deploy it to production, but you turn it off.
This is called Feature Flagging. Laravel 10 introduced a first-party package called Pennant to handle this elegantly.
In this guide, we will implement Pennant to control our AI features and build a sleek Admin UI using Beartropy to toggle them on/off without touching code.
🚩 Phase 1: Installation
First, install Pennant and publish the configuration.
1composer require laravel/pennant
2php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"
3php artisan migrate
Now, add the Pennant trait to your User model to unlock the magic methods.
1use Laravel\Pennant\Concerns\HasFeatures;
2
3class User extends Authenticatable
4{
5 use HasFeatures;
6}
🧠 Phase 2: Defining Features
Let's define our ai-writer feature. We want it to be active purely for internal employees for now.
Open App\Providers\AppServiceProvider.php:
1use Laravel\Pennant\Feature;
2use App\Models\User;
3
4public function boot(): void
5{
6 Feature::define('ai-writer', function (User $user) {
7 // Only users with @beartropy.com emails see this
8 return str_ends_with($user->email, '@beartropy.com');
9 });
10}
🎨 Phase 3: The UI Integration
Remember the CreateProduct form from yesterday? We don't want regular users seeing that AI button yet.
Blade makes this incredibly clean with the @feature directive.
1
2<x-bt-chat-input wire:model="description">
3
4 <x-slot:actions>
5
6 @feature('ai-writer')
7 <button wire:click="refineText" ...>
8 ✨ Rewrite with AI
9 </button>
10 @endfeature
11 </x-slot:actions>
12
13</x-bt-chat-input>
If I log in, I see the sparkles. If a customer logs in, they see a standard input. Zero code duplication.
🎛️ Phase 4: Building the Feature Manager
Defining features in code is fine, but a true "Admin Panel" lets you toggle features at runtime. Let's build a Livewire component ManageFeatures using Beartropy tables.
1namespace App\Livewire\Admin;
2
3use Livewire\Component;
4use Laravel\Pennant\Feature;
5use App\Models\User;
6
7class ManageFeatures extends Component
8{
9 public $features = ['ai-writer', 'new-dashboard', 'dark-mode-beta'];
10
11 public function toggleForUser($userId, $feature)
12 {
13 $user = User::find($userId);
14
15 // Toggle the feature state for this specific user
16 Feature::for($user)->active($feature)
17 ? Feature::for($user)->deactivate($feature)
18 : Feature::for($user)->activate($feature);
19 }
20
21 public function render()
22 {
23 return view('livewire.admin.manage-features', [
24 'users' => User::take(10)->get()
25 ]);
26 }
27}
The View (Beartropy Table):
1<x-bt-card title="Feature Rollout Manager">
2 <x-bt-table>
3 <x-slot:head>
4 <x-bt-table.heading>User</x-bt-table.heading>
5 @foreach($features as $feature)
6 <x-bt-table.heading class="text-center">{{ $feature }}</x-bt-table.heading>
7 @endforeach
8 </x-slot:head>
9
10 <x-slot:body>
11 @foreach($users as $user)
12 <x-bt-table.row>
13 <x-bt-table.cell>
14 <div class="font-medium">{{ $user->name }}</div>
15 <div class="text-xs text-gray-500">{{ $user->email }}</div>
16 </x-bt-table.cell>
17
18 @foreach($features as $feature)
19 <x-bt-table.cell class="text-center">
20
21 <x-bt-toggle
22 :checked="Feature::for($user)->active($feature)"
23 wire:click="toggleForUser({{ $user->id }}, '{{ $feature }}')"
24 color="success"
25 />
26 </x-bt-table.cell>
27 @endforeach
28 </x-bt-table.row>
29 @endforeach
30 </x-slot:body>
31 </x-bt-table>
32</x-bt-card>
Summary
With Laravel Pennant and Beartropy, you have effectively separated "Deployment" from "Release".
- Deploy your code on Friday (even if it's unfinished).
- Keep the flag OFF.
- On Monday morning, toggle the flag ON for 10% of users via your new Admin Dashboard.
This is how tech giants ship software. Now you can too.