Maintenance Mode
Two independent layers, both rendering the same branded maintenance page (see Error Pages — it's dispatched the same way, with "maintenance" as the pseudo status code):
- Global env switch —
APP_MAINTENANCE. An absolute kill switch: every portal, backoffice included, shows the maintenance page. No CLI access required, just flip the env var and deploy. - Per-portal toggle — controlled from Backoffice → Maintenance. Staff can take any single independent portal down (landing, app, or any custom stub) without touching the others.
Four portals never appear in that per-portal list — maintenance.exempt_portals — for two different reasons:
backoffice— staff always need a way in to flip things back.accountandauth— supportive portals for app/backoffice/any user-facing portal, not independent destinations of their own. Taking one down in isolation would just strand users mid-login or mid-account-task on whichever portal sent them there.api— not a UI portal at all; there's nothing for a maintenance page to render.
The global switch overrides every exemption above — if APP_MAINTENANCE=true, all four go down too.
APP_MAINTENANCE=true?
│
┌──────────┴──────────┐
yes no
│ │
EVERY portal down portal in exempt_portals?
(backoffice/account/auth/ (backoffice, account,
api included) auth, api)
│
┌──────────┴──────────┐
yes no
│ │
always passes through per-portal toggle on?
│
┌──────────┴──────────┐
yes no
│ │
maintenance page passes throughEnforcement
App\Http\Middleware\CheckMaintenance is appended globally to the web middleware group in bootstrap/app.php — it covers every current and future portal automatically, no per-route-file edit needed when you scaffold a new one with make:subdomain.
// bootstrap/app.php
$middleware->web(append: [
CheckMaintenance::class,
]);Config lives in config/maintenance.php:
'global' => (bool) env('APP_MAINTENANCE', false),
'exempt_portals' => ['backoffice', 'account', 'auth', 'api'],Per-portal toggle (Backoffice → Maintenance)
Backed by the portal_settings table (App\Models\PortalSetting) — one row per portal, with maintenance_mode and an optional custom message shown on that portal's maintenance page instead of the generic copy. Reads are cached for 5 minutes (Cache::remember, key portal-setting:{portal}) and busted on every save, so the toggle takes effect on the next request either way.
The backoffice page (resources/views/pages/backoffice/⚡maintenance/) lists every portal in config('multidomain.sub_domains') except those in maintenance.exempt_portals, with a switch and a message field per row.
Native Laravel maintenance mode
php artisan down / php artisan up still work exactly as normal Laravel — this starter kit doesn't touch that mechanism. It's a separate, CLI-only, whole-app switch. For per-portal granularity or an env-driven (no-CLI) toggle, use the system on this page instead.