tomatophp/filament-pwa
Filament PWA adds progressive web app support to your FilamentPHP panel, with install prompts, manifest and service worker settings managed right from the panel, plus a Blade directive for non-Filament pages.
The install path is short, but it touches four systems (Composer, Spatie settings, the TomatoPHP settings hub, and the panel provider), so do it in one pass on a scratch panel first and write down what actually happened.
composer require tomatophp/filament-pwa
# Spatie settings table (the package stores its PWA settings here)
php artisan vendor:publish --provider="Spatie\LaravelSettings\LaravelSettingsServiceProvider" --tag="migrations"
php artisan filament-settings-hub:install
# package-specific wiring: assets, defaults, service worker, manifest route
php artisan filament-pwa:install
php artisan migrate
Then a single line in app/Providers/Filament/AdminPanelProvider.php:
->plugin(\TomatoPHP\FilamentPWA\FilamentPWAPlugin::make())
And for every Blade view that is not a Filament panel page (login, landing, public app shell), drop the directive immediately above </head>:
@filamentPWA
</head>
Where to look first, in this order:
manifest.json lives there.php artisan vendor:publish --tag="filament-pwa-config". Read it before touching settings; it tells you what the settings page is actually writing to, and which filesystem disk the manifest and icons land on.filament-pwa:install command source — it is one command and it does the asset publishing; when an icon 404s or the manifest is stale, this is the file that explains why.First use case to prove: log into the admin panel over a real HTTPS origin on a real phone, get "Add to Home Screen" to appear, install it, open it standalone, then go offline and confirm what you get. That single loop exercises the manifest route, the icon disk, the service worker registration, and the cache policy — every one of which is a separate failure mode. Do not declare the install "done" until you have done it on a device, not just in Chrome DevTools' emulation mode.
Per-panel registration, with intent. The plugin is registered in the panel provider, so multi-panel apps get per-panel PWA support by construction. If a panel should not be installable (an internal tenant-admin, a read-only reporting panel), say so explicitly rather than relying on default behavior:
->plugin(
\TomatoPHP\FilamentPWA\FilamentPWAPlugin::make()
->allowPWASettings(false) // no settings page for this panel
)
v5 made allowPWASettings(false) genuinely per-panel and stopped the settings-hub plugin from being registered twice — treat that as the supported pattern and use it from day one instead of retrofitting it after a problem.
Gate the whole feature behind a flag while piloting. The plugin is a single ->plugin() line, so it is trivially conditional:
->when(
config('features.pwa'),
fn ($panel) => $panel->plugin(\TomatoPHP\FilamentPWA\FilamentPWAPlugin::make())
)
That gives you a one-env-var rollback ("disable the plugin") that does not require a redeploy of application code.
Treat settings as content, not code. The whole point of the Settings Hub integration is that a non-engineer can change the app name, the icon set, and the theme color without a deploy. Two consequences you should design around:
public, S3 + CDN, whatever you run) before you promise icon editing as an operator workflow.Use the Blade directive on the surfaces where users actually arrive. The pattern that pays off is putting @filamentPWA on the login page and any public/app-shell page, not just inside the panel. That is where the install prompt converts, and it is exactly the spot Filament-only PWA plugins forget. Because v5 renders the directive on every request and the panel works with PWA routes disabled, you can safely include it in a shared layout partial.
Publish and commit the override surface on day one.
php artisan vendor:publish --tag="filament-pwa-config"
php artisan vendor:publish --tag="filament-pwa-views"
php artisan vendor:publish --tag="filament-pwa-lang"
Published config/views in your repo do two things: they let you adjust behavior without waiting for upstream, and they keep a fork a realistic fallback rather than a rewrite. This is cheap insurance against a single-maintainer package.
Add a cache-busting step to your release checklist. Installed PWAs serve from cache; without a deliberate version bump the failure mode is "we deployed and nobody sees it." Whatever mechanism the package exposes for the asset/cache version, make the person running the deploy responsible for it by name. A release checklist line is worth more here than any code.
Verify the security boundary as a standing test, not a one-off. The specific script: log in, browse several pages, log out, go offline, try to navigate back to a cached panel page, and try it again from a second user account on the same browser profile. Run it on first install, and re-run it after every Filament major upgrade. If the service worker cannot be configured to exclude authenticated routes and non-GET responses, stop and re-scope to public pages only — do not put this in front of PII.
Sanity-check routes after install:
php artisan route:list | grep -i pwa
You want to see the manifest and service-worker endpoints and know their exact paths and scope. The service worker's scope determines which pages it can control; a worker served from a subpath will not control the root, which silently breaks the whole feature.
The authenticated-cache problem is the one that matters. A service worker sitting in front of Filament responses can serve stale or previously authenticated HTML after logout, or leak across users on a shared device. The README documents nothing about auth-aware cache exclusion, so this is yours to verify experimentally. Highest severity item on the list; test it before the package touches any panel holding real data.
Version pinning is not optional. This package tracks Filament majors with breaking ports (v4.0.0 for Filament v4, v5.0.0 for Filament v5 / Laravel 12–13 / PHP 8.2+), and the release notes explicitly tell v4 users to stay on the v4 branch. Pin ^5.0 if you are on Filament v5, pin the branch otherwise, and record the exact Filament/Laravel/PHP triple in your upgrade notes. Every Filament upgrade is a paired upgrade here — put it in the upgrade backlog, not in the "set and forget" pile.
Metadata hygiene before you quote a version. The listed last release date is ahead of the present, and the package description claims capabilities the README does not document (offline "behavior management" beyond what is spelled out). Confirm tags and installable versions on Packagist in a scratch environment before a plan references a specific version.
Settings-hub coupling means a second settings system. If your app already has its own settings mechanism, you are adding a parallel one for one feature. Make that trade consciously. Also note the install order: Spatie migrations → filament-settings-hub:install → filament-pwa:install. Skipping the settings-hub step leaves the plugin registered with nowhere to write, which surfaces as a confusing blank or missing settings page rather than a clean error.
Multi-panel and multi-tenant bleed. v5 fixed duplicate settings-hub registration and per-panel settings bleed, which means that class of bug has existed. If you run multiple panels or tenants, re-verify on your topology rather than trusting the changelog — check that changing a setting in panel A does not alter panel B, and that the settings page appears exactly once.
Icon and manifest uploads fail quietly on bad disks. If the configured disk is not publicly readable, you get a valid manifest pointing at unreachable icons; the install prompt then disappears or the installed app shows a blank icon, with nothing obvious in the logs. Check the manifest JSON in the browser and fetch an icon URL directly — a 403 there explains the whole symptom.
Installability needs a secure context. No HTTPS, no install prompt — full stop. localhost counts; a LAN IP over plain HTTP does not. Make sure staging is HTTPS-terminated, or you will spend a day debugging a feature that only works in production.
Debug in the Application tab, not the console. Chrome DevTools → Application → Manifest / Service Workers / Cache Storage tells you whether the manifest parsed, whether the worker activated, and exactly what got cached. "Update on reload" and "Clear site data" are your two most-used buttons during the spike. When behavior is inexplicable, unregister the worker and clear storage before theorizing.
Translations are uneven. Community locales exist (pt_BR, for instance) but coverage is not guaranteed. Publish filament-pwa-lang and check your actual locales render rather than assuming — missing keys on a settings page look like broken functionality.
No SLA, single maintainer. 98 stars, zero dependents, MIT, one primary author. That is fine for an internal admin capability; it is not fine as an unqualified dependency for a system of record. Name an internal owner for "who fixes the service worker?", and keep the published config and views in your repo so the fork option stays open.
Know where the ceiling is. This delivers installability plus basic asset caching for panels. No push notifications, no background sync, no write queue, no per-tenant manifest scoping. If the roadmap later needs tenant-branded manifests or queued offline writes, those are new builds — discovering that mid-sprint is the expensive path, so write the limit into the pilot brief.
How can I help you explore Laravel packages today?