lauroguedes/laravel-demo-mode
Laravel Demo Mode puts your app into a safe read-only state for public demos. It blocks destructive HTTP requests and database writes while allowing configurable exceptions, so visitors can explore without changing data. Includes middleware and helpers to detect demo mode and show a clear notice.
This is a small, low-traffic package (11 stars, unknown repository, score ~19/100), so treat the README and source as the source of truth before wiring it into anything real. The last release date (2026-09-26) also looks anomalous versus typical package timelines — confirm the tagged version and its Laravel compatibility before adopting.
Minimal setup:
composer require lauroguedes/laravel-demo-mode. The service provider should be auto-discovered; if not, register it in bootstrap/providers.php (Laravel 11+) or config/app.php.php artisan vendor:publish --provider="Lauroguedes\DemoMode\DemoModeServiceProvider" — check the provider namespace in vendor/ first because it may differ.config/demo-mode.php or similar) end to end. This is where the sandboxing switches live: likely a demo database connection, queue/mail/cache drivers, and a list of blocked routes or actions.Route::middleware(['web', 'demo'])->group(base_path('routes/demo.php'));
QUEUE_CONNECTION=sync or a dedicated queue, MAIL_MAILER=log, CACHE_STORE=array, and a fake/isolated filesystem disk.First use case: a public "try it" deployment of your app where visitors can click through real screens without touching production data. Start by demoing a single read-mostly flow (dashboard + one create action) behind the middleware, then expand.
Before going further, verify in source: what the middleware actually blocks, whether it swaps DB connections or merely filters routes, and whether it ships migrations/seeders for the demo dataset.
Dedicated route file + middleware group. Keep all demo-reachable routes in routes/demo.php under a single demo middleware group. This makes the sandbox boundary explicit and reviewable in one place rather than scattered across web.php.
Separate demo database connection. The most reliable pattern is a distinct connection in config/database.php (e.g. demo) backed by SQLite, with the package's config pointing at it. Reset the file between sessions or on a schedule rather than trying to reverse individual writes.
// config/database.php
'demo' => [
'driver' => 'sqlite',
'database' => storage_path('app/demo/demo.sqlite'),
'prefix' => '',
],
Reset-on-schedule instead of reset-on-request. A scheduled command (or the package's reset command, if provided) that re-runs migrate:fresh --seed --database=demo every N minutes is far more robust than per-request cleanup. Per-request teardown breaks under concurrency and queue workers.
Sync or isolated queues. Set the demo environment to QUEUE_CONNECTION=sync so jobs run inline and visibly, or a dedicated queue with a worker that only touches the demo DB. Never let demo jobs land on a production queue.
Neutralize outbound side effects. Set MAIL_MAILER=log, a sandboxed cache store (array or a prefixed Redis DB), and a fake filesystem disk. If the package exposes a "safe drivers" config block, use it; otherwise enforce via the demo .env.
Read-only enforcement at multiple layers. Middleware alone is not enough. Combine:
DELETE, bulk POST endpoints).false in demo context for admin actions.forceDelete).Session-per-visitor isolation. Give each visitor a fresh demo tenant/user (a pre-seeded demo account or a session-scoped SQLite copy). This is what makes a "playground" feel real without cross-visitor contamination — it's the pattern most demo-mode packages aim for, so check whether this one implements it or expects you to.
Rate limiting and bot protection. Wrap demo routes with throttle: middleware. A public playground is an abuse target; the package's safeguards are not a substitute for edge-level rate limiting.
Feature-flag the demo entry point. Expose a "Try the demo" link only when a config flag is on, so the same codebase serves both the real app and the playground.
src/ — especially the middleware and config — and confirm it's doing what the description claims. Budget for replacing it with ~100 lines of your own middleware if it doesn't fit.php artisan config:cache will freeze the demo connection and driver settings. If you switch environments on the same deploy, cache invalidation must be part of the deploy step.StartSession if it needs session-scoped state. A misordered stack can leak the production connection into a demo request.migrate:fresh on a shared file. If multiple requests or workers reset the SQLite file simultaneously, you'll get database is locked errors. Serialize resets with a lock (Cache::lock) or reset on a schedule rather than per request.demo disk pointing at a temp directory and prune it on the same schedule as the DB reset.Http::preventStrayRequests() in the demo environment or fake specific endpoints, otherwise visitors can trigger real third-party calls and costs.APP_DEBUG=false in demo. A stack trace on a public playground exposes config, paths, and sometimes credentials.array sessions don't persist across requests; database sessions on the demo connection get wiped by resets. Use a separate session table/connection or accept the reset.MAIL_MAILER=log, these flows silently "succeed" without sending. Either disable them in demo routes or surface the logged mail in a dev-only inbox UI.DemoContext-style service (or whatever the package exposes) injected via the container, so your own guards can ask isDemo() without hard-coding route names. If the package doesn't provide one, add a thin wrapper — it will outlive the package.composer.json and re-read the diff before upgrading. Don't use ^ ranges on a package that controls sandboxing.403/no-op. That test is your regression guard if the package changes or you swap it out.How can I help you explore Laravel packages today?