foxws / laravel-pwa
PWA manifest and Blade directives for Laravel.
Fund package maintenance!
Requires
- php: ^8.3
- illuminate/contracts: ^11.0||^12.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0.0||^9.0.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
README
A minimal, opinionated Progressive Web App (PWA) package for Laravel. It provides Blade directives for the PWA head and service worker registration, and an Artisan command to generate your manifest.json and publish a sw.js stub.
The included service worker uses a network-first strategy for navigation and cache-first for static assets, while intentionally bypassing the cache for Inertia.js (X-Inertia) and Livewire (X-Livewire) requests to prevent stale responses.
Installation
composer require foxws/laravel-pwa
Publish the config file:
php artisan vendor:publish --tag="pwa-config"
Usage
Blade directives
Add @pwaHead inside your <head> and @pwaSw just before </body>:
<head> @pwaHead </head> <body> ... @pwaSw </body>
This renders the theme-color meta tag, apple-touch-icon, manifest link, and the service worker registration script.
Both directives accept optional overrides:
@pwaHead(['themeColor' => '#ff0000', 'manifest' => '/custom.json']) @pwaSw(['swPath' => '/sw.js', 'scope' => '/', 'debug' => true])
Or use them as Blade components:
<x-pwa-head theme-color="#ff0000" /> <x-pwa-sw sw-path="/sw.js" scope="/" />
The @pwaSw directive automatically picks up the CSP nonce from Vite::cspNonce() when set.
Icons
Icons are defined in a dedicated icons array in config/pwa.php, separate from the manifest. Each entry supports a disk key pointing to any configured Laravel filesystem disk. The src URL is resolved at generation time via Storage::disk()->url(). Set disk to null to fall back to path used as-is.
The default configuration assumes three icons — a mobile icon (192×192), a desktop icon (512×512), and an apple-touch-icon. Create the storage symlink and place all files there:
php artisan storage:link
$ ls storage/app/public/images/icons storage/app/public/images/icons/apple-touch-icon.png storage/app/public/images/icons/icon-192x192.png storage/app/public/images/icons/icon-512x512.png
You can override each icon independently via .env:
PWA_ICON_MOBILE_PATH=/storage/images/icons/icon-192x192.png PWA_ICON_DESKTOP_PATH=/storage/images/icons/icon-512x512.png PWA_APPLE_TOUCH_ICON=/storage/images/icons/apple-touch-icon.png
For S3 or other remote disks, set the respective _DISK variable to the disk name — the URL will be resolved accordingly. Each icon can live on a different disk.
Generating the manifest and service worker
php artisan pwa:generate
This writes public/manifest.json from your config, and copies the sw.js stub to public/sw.js. Both paths are configurable via config/pwa.php.
The service worker serves an offline fallback page from public/offline.html. You must create this file yourself — see examples/offline.html for a starting point.
Configuration
// config/pwa.php return [ 'manifest_path' => env('PWA_MANIFEST_PATH', 'manifest.json'), 'sw_path' => env('PWA_SW_PATH', 'sw.js'), 'ignore_paths' => ['/api/', '/livewire/', '/_inertia/'], 'manifest' => [ 'id' => env('PWA_ID', '/'), 'name' => env('APP_NAME', 'Laravel'), 'short_name' => env('PWA_SHORT_NAME', 'Laravel'), 'description' => env('PWA_DESCRIPTION', 'A Progressive Web Application setup for Laravel projects.'), 'start_url' => env('PWA_START_URL', '/'), 'scope' => env('PWA_SCOPE', '/'), 'display_override' => ['fullscreen', 'standalone'], 'display' => env('PWA_DISPLAY', 'fullscreen'), 'orientation' => env('PWA_ORIENTATION', 'any'), 'background_color' => env('PWA_BACKGROUND_COLOR', '#ffffff'), 'theme_color' => env('PWA_THEME_COLOR', '#6777ef'), 'lang' => env('PWA_LANG', 'en'), 'dir' => env('PWA_DIR', 'ltr'), ], 'icons' => [ // Mobile icon [ 'disk' => env('PWA_ICON_DISK', null), 'path' => env('PWA_ICON_MOBILE_PATH', '/storage/images/icons/icon-192x192.png'), 'sizes' => env('PWA_ICON_MOBILE_SIZES', '192x192'), 'type' => env('PWA_ICON_MOBILE_TYPE', 'image/png'), ], // Desktop icon [ 'disk' => env('PWA_ICON_DISK', null), 'path' => env('PWA_ICON_DESKTOP_PATH', '/storage/images/icons/icon-512x512.png'), 'sizes' => env('PWA_ICON_DESKTOP_SIZES', '512x512'), 'type' => env('PWA_ICON_DESKTOP_TYPE', 'image/png'), ], ], 'apple_touch_icon' => env('PWA_APPLE_TOUCH_ICON', '/storage/images/icons/apple-touch-icon.png'), ];
Any key set to null in the manifest array is omitted from the generated JSON. Advanced keys such as shortcuts, screenshots, and categories can be added to the manifest array as needed.
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.