laravel-demo-mode maintained by lauroguedes
Laravel Demo Mode
Turn a Laravel installation into a public playground: seeded data, a scheduled reset, rotating credentials published on the login page, and a belt of restrictions that keeps a stranger from abusing the server.
Publishing a starter kit, a boilerplate or a SaaS with a browsable demo needs the same five pieces every time — a flag, a rebuild on a cycle, credentials a stranger can use that do not become a permanent fact of the internet, a list of things the demo must not do, and a visible notice that the data is temporary. This is those five pieces, extracted from two demos that have been running in public.
This package drops tables. Read docs/security.md before you install it anywhere. A wrong configuration erases your database.
Installation
composer require lauroguedes/laravel-demo-mode
php artisan demo:install
demo:install is additive: it publishes the config, appends the DEMO_ keys to
.env.example, and offers to write a DemoSeeder stub. It does not touch .env
and it does not turn the demo on — that is an act you perform on the deployment
you meant.
It asks two questions, because neither answer can be inferred from the project:
whether every visitor sees the same data or each gets
their own corner of it, and whether to start a DemoSeeder or
point the reset at a seeder you already have. Answer shared if you are not sure.
--sandbox=shared|scoped and --without-seeder answer both for a script, and a
run with nobody at the keyboard takes shared and writes the seeder.
Making an installation a demo
DEMO_MODE=true
DEMO_RESET_SCHEDULE="0 */6 * * *"
// config/demo.php
'environments' => ['demo'],
'allowed_hosts' => ['demo.example.com'],
Write the demonstration data into database/seeders/DemoSeeder.php, then check
your work:
php artisan demo:doctor
It exits non-zero on anything that would destroy data or publish a secret, so it belongs in your deploy pipeline ahead of the first reset.
Using it
use LauroGuedes\DemoMode\Facades\Demo;
Demo::enabled(); // the single point of truth
Demo::nextResetAt(); // derived from the cron, so a countdown cannot lie
Demo::credentials(); // what the login form should prefill
Demo::toArray(); // one payload for Blade, Livewire and Inertia
{{-- Both render nothing when this is not a demo, so no wrapper is needed --}}
<x-demo-banner /> {{-- a floating bar the package styles itself --}}
<x-demo-credentials />
@notdemo
<a href="{{ route('oauth.google') }}">Sign in with Google</a>
@endnotdemo
For Inertia, return Demo::toArray() from your own
HandleInertiaRequests::share(). For Blade and Livewire, the optional
ShareDemoState middleware puts the same payload in every view. See
docs/frontend.md.
Letting visitors reset it
'on_demand' => ['enabled' => true],
Off by default — it puts a migrate:fresh behind an HTTP request. Throttled,
cooled down, queued, CSRF-protected and host-checked; see
docs/on-demand-reset.md.
Isolating visitors from each other
'sandbox' => ['driver' => 'scoped'],
Each visitor gets the seeded baseline plus what they created. Not multi-tenancy and not a security boundary; see docs/sandbox.md.
Commands
| Command | What it does |
|---|---|
demo:install |
Publish the config, and a seeder stub if you want one |
demo:reset |
Rebuild the demonstration data. --dry-run prints the plan |
demo:status |
What this installation currently is |
demo:doctor |
Audit the configuration. Non-zero exit on anything dangerous |
demo:credentials |
Show, or --rotate, the published passwords |
demo:snapshot |
Capture the baseline the snapshot strategy restores |
demo:sandbox:prune |
Remove the sandboxes nobody came back to |
Working with an AI agent
If the project uses Laravel Boost, this
package ships its own guidelines and an agent skill. php artisan boost:install
picks them up with no configuration.
The guidelines are the short, always-loaded part: that this package drops tables,
that Demo::enabled() is the single source of truth, that the view components
already decide for themselves whether to render, and that a seeder must read the
published password from Demo::passwordFor() rather than hardcoding one — which
is the mistake that fails silently after the first rotation.
The demo-mode-development skill carries the rest, loaded only when it is
relevant: reset strategies, per-visitor isolation, cleaners, restrictions, write
guards and the on-demand reset.
What it is not
- Password-protecting a work in progress. That is
php artisan down --secret. - Backup and restore. That is
spatie/laravel-backup. - A demo data generator. The seeder is yours; the package runs it.
- Multi-tenancy. Visitor isolation is deliberately ephemeral and disposable.
Documentation
Full documentation is in docs/. Start with
security.md — it is the one that is not optional.
Already have a hand-rolled demo mode? UPGRADE.md is mostly a list of things to delete.
Testing
composer test
Credits
Extracted from lauroguedes/laravel-sso and lauroguedes/mary-ui-starter-kit, which had each solved this badly in their own way first.
License
The MIT License. See LICENSE.md.