Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Mobile Pass Laravel Package

spatie/laravel-mobile-pass

Generate Apple Wallet and Google Wallet passes for boarding passes, event tickets, coupons, store cards, membership cards, and gift cards—with push updates to keep issued passes current on users' devices.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish. The package is a normal Laravel package: composer require spatie/laravel-mobile-pass, then publish the config and migrations (vendor:publish --tag="mobile-pass-config" and --tag="mobile-pass-migrations") and run php artisan migrate. The migration creates the mobile_passes table plus the Google event bookkeeping tables used by the callback endpoint.

Where to look first. Three places, in this order:

  1. config/mobile-pass.php — it is the map of everything you will actually need to change. It holds the Apple block (certificate path + password, team identifier, pass type identifier, and webservice.host), the Google block (issuer ID, service-account credentials), the models.mobile_pass override, and the queue connection used for update pushes.
  2. The docs site (spatie.be/docs/laravel-mobile-pass) under basic-usage/generating-your-first-pass — the README is intentionally a teaser; the real API surface lives there.
  3. spatie/laravel-mobile-pass-demo — a working app that generates all five pass types and pushes a live update. When a pass silently fails to install, diffing against the demo is faster than reading Apple's spec.

Credentials are the real setup cost. Apple requires a Pass Type ID with its own certificate (.p12 + password) and a Team ID; Google requires a Wallet API issuer account with the service account added as a user. Budget an afternoon for provisioning and almost none for code. Apple passes also cannot be validated in the iOS Simulator — you need a real device to confirm the .pkpass installs.

First use case. An event ticket is the best smoke test because it exercises fields, images, dates, and updates all at once:

use Spatie\LaravelMobilePass\Builders\Apple\EventTicketPassBuilder;

$pass = EventTicketPassBuilder::make()
    ->setEventName('Laracon Europe 2026')
    ->setHolderName($order->user->name)
    ->setStartDate($order->event->starts_at)
    ->setEndDate($order->event->ends_at)
    ->setLocation($order->event->venue_name, $order->event->lat, $order->event->lng)
    ->save();

return $pass->addToWalletUrl(); // signed .pkpass download

addToWalletUrl() is deliberately unified across platforms, so the same "Add to Wallet" button in your Blade view works for both. Google passes are built with the equivalent builder under Spatie\LaravelMobilePass\Builders\Google\….

Implementation Patterns

Model your pass as a side effect of a domain event, not as a resource. The pattern that holds up is: an order/payment/registration is the source of truth, and a CreateMobilePass action is dispatched from a listener. Store the resulting mobile_passes foreign key on your order row so you can re-issue, revoke, or push later. Builders are fluent and throwaway; the saved MobilePass model is the durable artifact.

One class per real-world thing, one object per person (Google). Google Wallet separates a class (the template — event, venue, branding) from an object (the individual attendee's pass). Building the class once per event and only creating objects on purchase avoids hitting quota and keeps branding consistent:

EventTicketPassClass::make('laracon-eu-2026')
    ->setEventName('Laracon Europe 2026')
    ->addLocation(52.3676, 4.9041)
    ->addLink('https://laracon.eu', 'Event site')
    ->save();

The 1.3.0/1.6.0 additions — locations, links, text modules, image modules — exist on both classes and objects and hydrate back through find()/all(), so treat the class as config-as-code you can re-run idempotently in a seeder.

Wire updates to model saves, but keep them off the request path. MobilePass::boot() is platform-aware and dispatches PushPassUpdateJob when the pass changes, so the idiomatic flow is simply $pass->update([...]). Set MOBILE_PASS_QUEUE_CONNECTION to a real queue in production — otherwise a customer-facing request blocks on Apple's and Google's APIs. Use $pass->expire() for the unified "void this pass" case (refund, cancelled event, revoked membership); it handles both platforms.

Use the Google callbacks as a state machine, not a log. The /mobile-pass/google/callbacks endpoint fires GoogleMobilePassSaved / GoogleMobilePassRemoved and populates $pass->googleEvents. The practical use is gating: only push updates to users who actually have the pass installed ($pass->isCurrentlySavedToGoogleWallet()), and treat "removed" as a signal to stop notifying rather than deleting your record.

Testing and local workflows. Wrap builder calls behind your own action class and inject it, so feature tests can assert "a pass was created for this order" without touching certificates. Use Queue::fake() to assert the update job is dispatched rather than asserting the HTTP payload. Build the pass payload in a test and snapshot the JSON — most regressions in this package's history are payload-shaped (missing backFields, dropped "0" values, missing timeStyle), and a snapshot catches them cheaply.

Gotchas and Tips

  • The webservice host must be HTTPS. Since 1.0.2 the Apple builder throws InvalidConfig::webserviceHostMustBeHttps instead of emitting a pass Apple will reject. For local development that means a TLS tunnel (Expose, ngrok, Valet share) — localhost will not cut it, and the failure mode on-device is silent.
  • Silent install failures are almost always identity mismatches. The Pass Type ID in mobile-pass.apple.pass_type_identifier must match the certificate's, and the certificate must be current. If the .pkpass downloads but Wallet refuses it, check that before checking your field definitions.
  • Apple updates require device registration. A pass only receives pushes after iOS registers with your webServiceURL; the package routes those lookups by pass_serial (added in 1.0.5 along with a required migration — read UPGRADING.md, don't skip it on an existing install). A pass that never updates is usually a registration problem, not a job problem.
  • The queue defaults to sync. If pass updates feel slow or your checkout endpoint times out on Google API calls, you haven't set MOBILE_PASS_QUEUE_CONNECTION and a worker.
  • Custom models must be honored everywhere. Overriding mobile-pass.models.mobile_pass works across the builder and the check-for-updates request as of 1.2.1 — you must extend the package model and add the reverse relations yourself, and older versions silently ignored the override in some paths.
  • Images have asymmetric support. Use Image::fromUrl() for Google; local-path hosting on Google objects is explicitly deferred, so a public_path() image that works for Apple may simply not render on Google. Apple supports setStripImage, setThumbnailImage, and (since 1.4.0) setBackgroundImage / setRemoteBackgroundImage, each bundled with @2x/@3x densities — supply the densities or the pass looks blurry on modern devices.
  • Serialization edge cases. The package has had repeated fixes around falsy values ("0" dropped in Apple entity serialization, fixed 1.8.1) and missing backFields on StoreCard/Coupon passes (1.0.1). If you depend on a zero balance, a zero points total, or back-of-pass terms, pin a recent version and add a test.
  • Callback verification is ECv2SigningOnly as of 1.2.0, and automatic JWKS fetching is still deferred — verify the signing key configuration against Google's console or your callbacks will be rejected with little diagnostic output.
  • .pkpass is served Content-Disposition: inline since 0.1.1 so Safari opens the Wallet preview; non-Safari browsers download it. Don't "fix" this to attachment — it breaks the iPhone flow that most of your traffic uses.
  • Locales. i18n arrived for Google in 1.7.0 and Apple in 1.7.1. If you ship a multilingual audience, set locale data on the builder rather than baking translated strings into fields, or the pass will not follow the user's device language.
  • Extension points. The builders are the intended customization surface; subclass them or wrap them in your own actions rather than editing published views or the package's payload assembly. For anything the builders don't expose (Smart Tap/NFC beyond iBeacon, which landed in 1.5.0), check the changelog first — the surface has expanded steadily and most "missing features" are version lag.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log