spatie/laravel-demo-mode
Protect work-in-progress Laravel apps from prying eyes with a demo-mode middleware. Redirects visitors (including unknown routes) to an “under construction” URL until they visit a configurable access URL (e.g. /demo) to unlock protected routes.
Installation:
composer require spatie/laravel-demo-mode
Publish the config file:
php artisan vendor:publish --provider="Spatie\DemoMode\DemoModeServiceProvider"
Configure Routes:
demo middleware to routes you want to protect in routes/web.php:
Route::middleware(['demo'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
});
/demo) in config/demomode.php:
'unlock_url' => '/demo',
'redirect_url' => '/under-construction',
First Use Case:
/demo to "unlock" the app./dashboard) will now be accessible until the session expires or the browser is closed./under-construction.Temporary Access for Clients/Stakeholders:
demo middleware for client demos or internal previews.Route::middleware(['demo'])->group(function () {
Route::get('/', [HomeController::class, 'index']);
Route::get('/products', [ProductController::class, 'index']);
});
Environment-Specific Activation:
local or staging environments by checking the config:
if (app()->environment('local')) {
Route::middleware(['demo'])->group(...);
}
Custom Unlock Logic:
Route::get('/demo', function () {
if (request()->input('password') === config('demomode.unlock_password')) {
session()->put('demo_mode_unlocked', true);
return redirect()->intended('/');
}
return back()->withErrors(['password' => 'Incorrect password']);
});
Integration with Authentication:
Route::middleware(['auth', 'demo'])->group(function () {
Route::get('/admin', [AdminController::class, 'index']);
});
file, database, or redis).Route::middleware(['demo', 'api'])->group(function () {
Route::get('/api/preview', [ApiController::class, 'preview']);
});
$this->actingAs($user)->withSession(['demo_mode_unlocked' => true]);
Session Expiry:
session()->put('demo_mode_unlocked', true) with a longer session.lifetime in .env.Caching Issues:
?demo=1).CSRF Token Conflicts:
/demo) uses a form, ensure CSRF protection is disabled or handled:
Route::post('/demo', [DemoController::class, 'unlock'])->middleware('throttle:10,1');
Middleware Order:
demo middleware after web but before route-specific middleware (e.g., auth). Incorrect ordering may cause unintended redirects.dd(session()->all()); // Verify 'demo_mode_unlocked' exists.
handle method in app/Http/Middleware/DemoMode.php:
\Log::debug('Demo mode redirect triggered for: ' . $request->path());
config(['demomode.redirect_url' => '/maintenance']);
unlock logic to support multiple paths:
if (request()->is('demo') || request()->is('preview')) {
session()->put('demo_mode_unlocked', true);
}
Custom Unlock Conditions:
Override the unlock logic in a custom middleware:
public function handle($request, Closure $next)
{
if ($this->isUnlocked($request)) {
return $next($request);
}
return redirect(config('demomode.redirect_url'));
}
protected function isUnlocked($request)
{
return session()->has('demo_mode_unlocked') ||
$request->ip() === config('demomode.trusted_ip');
}
Event-Based Unlocking: Trigger unlock via events (e.g., after user registration):
event(new DemoModeUnlocked($user));
Listen for the event in a service provider:
DemoModeUnlocked::class => function ($event) {
session()->put('demo_mode_unlocked', true);
},
Database-Backed Unlocks: Store unlock status in the database for persistence across sessions:
if (DemoUnlock::where('ip', $request->ip())->exists()) {
session()->put('demo_mode_unlocked', true);
}
How can I help you explore Laravel packages today?