spatie/laravel-mobile-pass
Generate Apple Wallet and Google Wallet passes in Laravel (tickets, boarding passes, coupons, membership cards). Create and sign pass files, serve them to users, and push updates to installed passes to keep details current across devices.
Installation:
composer require spatie/laravel-mobile-pass
php artisan vendor:publish --provider="Spatie\MobilePass\MobilePassServiceProvider"
php artisan migrate
Configure .env:
MOBILE_PASS_APPLE_WEBSERVICE_HOST=https://your-app.com
MOBILE_PASS_APPLE_TEAM_ID=your_apple_team_id
MOBILE_PASS_APPLE_CERTIFICATE_PATH=/path/to/certificate.p12
MOBILE_PASS_APPLE_CERTIFICATE_PASSWORD=your_password
MOBILE_PASS_GOOGLE_ISSUER_ID=your_google_issuer_id
MOBILE_PASS_GOOGLE_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----
First Use Case: Generate a boarding pass for a user:
use Spatie\MobilePass\PassBuilders\Apple\BoardingPassBuilder;
$pass = BoardingPassBuilder::create()
->setSerialNumber('BP12345')
->setOrganizationName('Your Company')
->setHeaderFields([
'primary' => 'Boarding Pass',
'secondary' => 'Flight 123',
])
->setBarcode('1234567890')
->setStoreCardFields([
'primary' => 'John Doe',
'secondary' => 'Passenger',
])
->setExpirationDate('2025-12-31')
->build();
$pass->save();
Deliver to User:
return response()->download($pass->pkpassPath());
// OR for Google Wallet:
return redirect($pass->addToWalletUrl());
Builder Pattern: Use PassBuilder classes (e.g., BoardingPassBuilder, EventTicketBuilder) for type-specific configurations.
$pass = (new BoardingPassBuilder())
->setHeaderFields(['primary' => 'Event Ticket', 'secondary' => 'Conference 2024'])
->setBarcode('EVENT2024')
->setExpirationDate('2024-12-31')
->setBackFields(['header' => 'Thank You!', 'message' => 'Visit us again.'])
->build();
Dynamic Fields:
$pass->setStoreCardFields([
'primary' => $user->name,
'secondary' => $user->membership_tier,
]);
Images:
$pass->setLogoImage(Image::fromUrl('https://example.com/logo.png'));
$pass->setIconImage(Image::fromPath(public_path('images/icon.png')));
Apple Wallet:
webServiceURL (configured in .env)..pkpass bundle.$pass->setRelevance([
'iBeacon' => [
'uuid' => 'E2C56DB5-DFFB-48D2-B060-D0F5A71096E0',
'major' => 1,
'minor' => 1,
'relevance' => 'immediate',
],
]);
Google Wallet:
issuerId and privateKey for signing.EventTicket, Loyalty, Offer, and Generic pass types.GoogleMobilePassSaved events).Push Updates:
$pass->update([
'expirationDate' => '2025-06-30',
'barcode' => 'NEWBARCODE123',
]);
// Triggers `PushPassUpdateJob` (queued by default).
Expiry:
$pass->expire(); // Marks pass as expired in both Apple/Google systems.
Apple:
return response()->download($pass->pkpassPath());
// OR direct download link:
$pass->addToWalletUrl(); // Returns URL to trigger Safari Wallet preview.
Google:
return redirect($pass->addToWalletUrl());
// OR for direct download:
$pass->googlePassJsonPath();
use Spatie\MobilePass\Events\GoogleMobilePassSaved;
GoogleMobilePassSaved::listen(function (GoogleMobilePassSaved $event) {
// Log or trigger follow-up actions.
});
MobilePass model:
class CustomMobilePass extends \Spatie\MobilePass\Models\MobilePass
{
protected $table = 'custom_mobile_passes';
}
config/mobile-pass.php:
'models' => [
'mobile_pass' => \App\Models\CustomMobilePass::class,
],
.env:
MOBILE_PASS_QUEUE_CONNECTION=redis
$pass->pushUpdate();
Image::fromUrl() for dynamic images:
$pass->setLogoImage(Image::fromUrl($user->profileImageUrl));
$pass->setNfcFields([
'message' => 'Tap to unlock gate',
'url' => 'https://example.com/validate',
]);
$pass->setRelevance([
'iBeacon' => [
'uuid' => 'E2C56DB5-DFFB-48D2-B060-D0F5A71096E0',
'major' => 1,
'minor' => 1,
'relevance' => 'immediate',
],
]);
MobilePass model for unit tests:
$pass = Mockery::mock(\Spatie\MobilePass\Models\MobilePass::class);
$pass->shouldReceive('save')->once();
MOBILE_PASS_APPLE_WEBSERVICE_HOST throws InvalidConfig::webserviceHostMustBeHttps..env uses https:// (e.g., https://your-app.com).pass_serial. Duplicate serials cause conflicts.$pass->setSerialNumber(Uuid::generate()->toString());
ECv2SigningOnly for callback verification (v1.2.0+).Image::fromUrl() with absolute URLs:
Image::fromUrl('https://cdn.example.com/image.jpg');
isExpired() before processing updates:
if (!$pass->isExpired()) {
$pass->pushUpdate();
}
pass_serial migration.php artisan migrate --path=/vendor/spatie/laravel-mobile-pass/database/migrations
E2C56DB5-DFFB-48D2-B060-D0F5A71096E0) and ensure values match your beacon setup.How can I help you explore Laravel packages today?