Laravel Pulse & Pennant: Monitor & Feature Flags in Production
Learn to monitor Laravel apps with Pulse and manage feature flags with Pennant.
20+ years shipping production PHP systems at scale. Everything here is grounded in real deployments.
- ✓Basic knowledge of Laravel (routes, controllers, Blade)
- ✓PHP 8.1+
- ✓Laravel 10.x or 11.x
- ✓Composer installed
- Laravel Pulse provides real-time monitoring for queues, requests, and more.
- Laravel Pennant simplifies feature flag management with database or array drivers.
- Both are first-party packages for production-grade apps.
- Pulse helps identify bottlenecks; Pennant enables gradual rollouts.
- Use Pulse's dashboard and Pennant's Blade directive for easy integration.
Think of Pulse as a car's dashboard that shows speed, fuel, and engine health—it helps you spot problems before they break down. Pennant is like a light switch for new features: you can turn them on for a few users first, then everyone, without redeploying.
Building a Laravel application is exciting, but running it in production brings challenges: slow responses, queue failures, and risky feature rollouts. Laravel Pulse and Pennant are two first-party packages that address these pain points. Pulse gives you a real-time dashboard to monitor your app's health—request rates, queue throughput, slow queries, and more. Pennant lets you manage feature flags, enabling you to gradually release features to subsets of users without deploying new code. In this tutorial, you'll learn to install and configure both packages, write custom monitors, and use feature flags in controllers and Blade templates. We'll also cover a real-world production incident where Pulse caught a memory leak and Pennant saved a rollout. By the end, you'll be equipped to monitor and control your Laravel app like a pro.
Installing and Configuring Laravel Pulse
Laravel Pulse is a real-time monitoring package. Install it via Composer: composer require laravel/pulse. Then publish the config and migrations: php artisan vendor:publish --provider="Laravel\Pulse\PulseServiceProvider". Run migrations: php artisan migrate. Pulse uses a separate queue connection named 'pulse' to avoid interfering with your main queue. Configure it in config/queue.php:
``php 'connections' => [ 'pulse' => [ 'driver' => 'database', 'table' => 'pulse_jobs', 'queue' => 'default', 'retry_after' => 90, ], ], ``
Then set PULSE_QUEUE_CONNECTION=pulse in your .env. Finally, start the Pulse worker: php artisan pulse:check. Access the dashboard at /pulse (ensure the route is registered in routes/web.php).
memory_limit in php.ini to at least 128MB.Custom Pulse Recorders
You can create custom recorders to monitor specific metrics. For example, monitor the number of failed login attempts. Create a recorder class that implements Laravel\Pulse\Recorders\Concerns\Ignores and uses the Laravel\Pulse\Recorders\Concerns\Sampling trait. Register it in config/pulse.php under recorders. Here's an example:
```php namespace App\Pulse;
use Laravel\Pulse\Recorders\Concerns\Ignores; use Laravel\Pulse\Recorders\Concerns\Sampling; use Laravel\Pulse\Pulse;
class FailedLogins { use Ignores, Sampling;
public function __construct(protected Pulse $pulse) { // }
public function register(): void { $this->pulse->event('failed_login', function () { // Increment counter $this->pulse->increment('failed_login_count'); }); } } ```
Then in AppServiceProvider::boot(), listen to the event and call the recorder. Pulse will display the metric on the dashboard if you create a custom card.
Sampling trait to record only a percentage of events.Installing and Configuring Laravel Pennant
Laravel Pennant manages feature flags. Install: composer require laravel/pennant. Publish config: php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider". Run migrations: php artisan migrate. Define features in App\Features class. Example:
```php namespace App\Features;
use Laravel\Pennant\Feature;
class NewCheckout { public function resolve(User $user): bool { return $user->id <= 100; // Roll out to first 100 users } } ```
Use in controllers:
```php use App\Features\NewCheckout; use Laravel\Pennant\Feature;
if (Feature::active(NewCheckout::class)) { // new checkout logic } ```
In Blade:
``blade @feature('new-checkout') <p>New checkout experience</p> @endfeature ``
You can also use scopes:
``php Feature::for($user)->active('new-checkout'); ``
pennant:cache command and clear cache when flags change.Advanced Pennant: Percentage Rollouts and A/B Testing
Pennant supports percentage-based rollouts and A/B testing. Use the percentage method:
``php Feature::define('new-checkout', function (User $user) { return Feature::percentage(50); // 50% of users }); ``
For A/B testing, use Feature::for($user)->active('new-checkout') and track conversions. You can also use the Feature::when method to execute code conditionally:
``php Feature::when('new-checkout', function () { // new checkout }, function () { // old checkout }); ``
You can also use database-driven flags by storing resolutions in the pennant_features table. Use the Laravel\Pennant\Drivers\DatabaseDriver driver in config/pennant.php:
``php 'driver' => env('PENNANT_DRIVER', 'database'), ``
Then manage flags via a UI or CLI.
Integrating Pulse and Pennant: Monitoring Feature Flag Adoption
You can create a Pulse recorder to monitor how many users are seeing a feature flag. For example, track the number of times a feature is active:
```php namespace App\Pulse;
use Laravel\Pulse\Pulse;
class FeatureFlagUsage { public function __construct(protected Pulse $pulse) {}
public function register(): void { $this->pulse->event('feature_flag_checked', function (string $feature) { $this->pulse->increment('feature_flag_' . $feature); }); } } ```
Then in your app, fire the event when checking a flag:
```php use App\Pulse\FeatureFlagUsage; use Illuminate\Support\Facades\Event;
Event::dispatch('feature_flag_checked', 'new-checkout'); ```
This allows you to see adoption rates on the Pulse dashboard.
Security Best Practices for Feature Flags
Feature flags can expose unfinished features. Always restrict access to authorized users. Use middleware to protect routes:
``php Route::middleware(['auth', 'feature:new-checkout'])->group(function () { Route::get('/checkout', [CheckoutController::class, 'index']); }); ``
Define a custom middleware:
```php namespace App\Http\Middleware;
use Closure; use Laravel\Pennant\Feature;
class EnsureFeatureIsActive { public function handle($request, Closure $next, $feature) { if (! Feature::active($feature)) { abort(404); } return $next($request); } } ```
Also, ensure that feature flag values are not exposed to the client side unless intended. Use environment-specific configurations and never hardcode sensitive flags.
The Silent Queue Failure: How Pulse Saved Our App
- Monitor queue throughput and failure rates in production.
- Set memory limits for queue workers.
- Use Pulse to detect silent failures.
- Always log job failures even if retried.
- Implement health checks for critical background processes.
php artisan optimize:clear.php artisan pulse:check). Check queue connection and supervisor configuration.App\Features class. Check for typos.php artisan pulse:checkphp artisan queue:work --queue=pulse| File | Command / Code | Purpose |
|---|---|---|
| config | return [ | Installing and Configuring Laravel Pulse |
| app | namespace App\Pulse; | Custom Pulse Recorders |
| app | namespace App\Features; | Installing and Configuring Laravel Pennant |
| routes | use Illuminate\Support\Facades\Route; | Advanced Pennant |
| app | namespace App\Providers; | Integrating Pulse and Pennant |
| app | namespace App\Http\Middleware; | Security Best Practices for Feature Flags |
Key takeaways
Common mistakes to avoid
4 patternsNot running the Pulse worker in production
Hardcoding feature flag values in controllers
Forgetting to cache Pennant flags in production
Exposing feature flag states to the frontend without authentication
Interview Questions on This Topic
What is the purpose of Laravel Pulse?
Frequently Asked Questions
20+ years shipping production PHP systems at scale. Everything here is grounded in real deployments.
That's Laravel. Mark it forged?
3 min read · try the examples if you haven't