Appearance
Running a public demo
A demo somebody can click into sells the kit; a demo they have to be told a password for does not. Demo mode is the difference: one environment variable turns the landing page and the login screen into three buttons, Sign in as Admin, Sign in as Organization owner, Sign in as Member, each of which drops the visitor straight into the product wearing that role.
Never turn this on in production
A request to the route this registers is a session as a seeded administrator with no password and no second factor. That is not a weakness in the implementation, it is the feature. Turn it on only on a throwaway installation whose database is DemoSeeder's output and is wiped on a schedule.
Turning it on
bash
LARASPRING_DEMO=trueThen seed, and the accounts the buttons use exist:
bash
php artisan db:seed --class=DemoSeederThat is the whole setup. With the flag off, which is the default and every installation you ship to a customer, none of this exists: the route is never registered, the shared prop is null, and no demo markup is drawn on any page.
What it adds
A thin banner on every shell, public and signed-in, saying this is a demo and the data resets. It is deliberately quieter than the impersonation banner: that one warns about something you are doing, this one is context about where you are, and it can be closed for the tab.
An "Explore the demo" block on the landing page and the login screen, with one button per role.
One route, POST /demo/login, named demo.login and rate limited.
The three accounts
They are an allow-list in config/demo.php, keyed by a short identifier. The identifier is what the browser sends; an email address never travels in the request, so the worst a crafted POST can do is name a key that is not there and be refused.
| Key | Account | What the visitor sees |
|---|---|---|
admin | Ada Lovelace, admin@example.com | An administrator, and the owner of Initech. The only one who can reach /admin. |
owner | Grace Hopper, owner@example.com | Owner of Northwind Traders, the organization with an active subscription and pending invitations. |
member | Margaret Hamilton, designer@example.com | A plain member of Northwind. The same organization, none of the management screens. |
Change the roles you offer by editing that file. Each entry needs an email that DemoSeeder actually creates and a label_key that both locales define, and tests/Feature/I18n/TranslationParityTest.php will catch the second half of that if you forget.
Why none of them has a second factor
Nothing in demo mode skips the two-factor challenge. The sign-in is an ordinary Auth::login(), which means an allow-listed account that had enrolled would give you a button that dead-ends on a screen asking for a phone the visitor does not have.
So the constraint lives in the seeder instead of in a bypass: DemoSeeder puts its two confirmed credentials on admin.northwind@example.com and support@example.com, neither of which is on the list, and its half-finished enrolment on dev@example.com. Signing in as those from the login form still demonstrates the challenge, with the recovery codes printed at the end of the seed run.
tests/Feature/DemoModeTest.php asserts the property directly, so moving a credential onto a demo account fails the suite rather than the demo.
Resetting the data hourly
Demo mode does not schedule anything, on purpose: what "reset" means depends on where you host it. The seeder is narrowly idempotent, it removes the accounts and organizations it owns along with everything hanging off them and writes them again, and it leaves alone anything an evaluator created for themselves. So the reset is one command:
php
// routes/console.php
use Illuminate\Support\Facades\Schedule;
Schedule::command('db:seed', ['--class' => 'DemoSeeder', '--force' => true])->hourly();--force because seeding is refused in production without it, and a demo runs with APP_ENV=production like anything else behind a real domain.
If you would rather start from nothing every hour, migrate:fresh --seed --seeder=DemoSeeder is the blunter version. It is also the one that deletes whatever a visitor was in the middle of, which is why the seeder is the default suggestion here.
Whatever you pick, say the same thing on the page as in the schedule. The banner string is app.demo.banner in lang/{locale}/app.php, and a demo that promises an hourly reset and delivers a daily one is worse than one that promises nothing.
Keep it out of search results
A demo installation and the site selling the product are two hosts with substantially the same copy, and the one you want indexed is not the demo. Serve a robots.txt that says so:
# public/robots.txt on the demo host only
User-agent: *
Disallow: /Add <meta name="robots" content="noindex"> to resources/views/app.blade.php if you want belt and braces. Guard it on the flag, so the same build serving a customer's production site is unaffected:
blade
@if (config('demo.enabled'))
<meta name="robots" content="noindex, nofollow">
@endifWhat demo mode is not
It is not a sandbox. The demo accounts can do everything the real ones can, including sending invitations to any address they type and reaching a billing checkout. Set LARASPRING_BILLING_PROVIDER to a test-mode key, use a mail transport that swallows what it is given, MAIL_MAILER=log is enough, and treat every byte in that database as public.
It is also not a permission system. There is no way to offer a "read-only" demo account, because the roles it offers are the product's own roles and the product does not have one.