This guide is a short reference for the five pass types wallet_passes supports, the platform-specific resources each one maps to, and the PassData fields that are meaningful per type. The library exposes pass type as a single atom on PassData.pass_type and threads it through both the Apple .pkpass builder and the Google Wallet API client.

Forward links:

Overview

A "pass type" describes the shape of the pass: an event ticket renders differently from a boarding pass, and a loyalty card differently again. Both Apple and Google model this, but they expose it in different shapes of their API. This library normalises both behind a single atom on PassData:

%WalletPasses.PassData{pass_type: :event_ticket, ...}

The default is :event_ticket. The five supported atoms are :event_ticket, :boarding_pass, :store_card, :coupon, and :generic (note: :store_card, not :loyalty — see the table below).

Concepts

Apple: a top-level "style key" inside pass.json

Apple's pass.json carries exactly one of eventTicket, boardingPass, storeCard, coupon, or generic as a top-level key. Whichever key is present determines how iOS lays out the pass; the value is the "pass-structure dictionary" that holds the field arrays (headerFields, primaryFields, secondaryFields, auxiliaryFields, backFields) and, for boarding passes only, the transitType. This library picks the correct top-level key from pass_data.pass_type.

Google: a different resource per type

Google Wallet's REST API exposes five different resource families. Each pass type has its own class endpoint, its own object endpoint, and its own JWT payload key for the Save-to-Wallet link. There is no single "pass" endpoint; you must hit the right resource for the type you want. This library resolves all three from pass_data.pass_type via WalletPasses.PassType so callers don't have to think about it.

Type Mapping

pass_typeApple style keyGoogle classGoogle objectSave-URL JWT keyClass ID suffix
:event_ticketeventTicketeventTicketClasseventTicketObjecteventTicketObjectsevent_class
:boarding_passboardingPassflightClassflightObjectflightObjectsflight_class
:store_cardstoreCardloyaltyClassloyaltyObjectloyaltyObjectsloyalty_class
:couponcouponofferClassofferObjectofferObjectsoffer_class
:genericgenericgenericClassgenericObjectgenericObjectsgeneric_class

Two naming asymmetries to note:

  • Apple calls a loyalty pass a "store card" (storeCard); Google calls it "loyalty" (loyaltyClass/loyaltyObject). This library uses the atom :store_card — pick whichever name you prefer, but the atom is :store_card.
  • Apple calls flight/transit passes "boarding pass"; Google calls the same resource flightClass/flightObject. The library accepts :boarding_pass for both.

Field Applicability

Most PassData fields apply to every pass type. The field arrays (header_fields, primary_fields, secondary_fields, auxiliary_fields, back_fields) are universal — they always populate the corresponding Apple pass-structure section, and on Google they flatten into textModulesData entries (Google doesn't have separate "primary/secondary" zones in the same way).

The fields below are either type-conditional or get mapped to different Google object fields depending on type:

PassData field:event_ticket:boarding_pass:store_card:coupon:generic
holder_nameGoogle: ticketHolderNameGoogle: passengerNameGoogle: accountNamenot mappedGoogle: header.defaultValue (top-line text)
transit_typeignoredrequired (defaults :air)ignoredignoredignored
event_namesuggested for class titlesuggested for class titlesuggested for class titlesuggested for class titlesuggested for class title

A few notes on the above:

  • holder_name mapping. On Apple, holder_name isn't emitted directly into pass.json — surface it through your fields (e.g. a primary or secondary field) if you want it visible. On Google, the library writes it into a type-appropriate Google object property as shown.
  • event_name is a PassData field, but Apple doesn't read it. It's intended as data your PassDataProvider carries through to the Google class builder, where it maps to the type-specific class title field (see "Class-shape differences" below). On Apple, set the visible title through Apple.Visual.logo_text and description.
  • NFC fields, location fields, dates, and barcode fields are type-independent. They apply identically to every pass type. See the NFC & Smart Tap guide for NFC specifics.

transit_type for :boarding_pass

When pass_type: :boarding_pass, the Apple builder writes a transitType key into the pass-structure dictionary. The atom on PassData.transit_type maps to Apple's PKTransitType* enum:

transit_type atomApple transitType value
:airPKTransitTypeAir
:boatPKTransitTypeBoat
:busPKTransitTypeBus
:trainPKTransitTypeTrain
:genericPKTransitTypeGeneric

nil defaults to :air. The field is ignored for every pass type other than :boarding_pass — setting it on a :store_card won't produce transitType in the output. Google's flightClass/flightObject resources don't take an equivalent enum; the library just emits the flight resource shape and Google infers transit context from the resource type itself.

%WalletPasses.PassData{
  serial_number: "BP-001",
  pass_type: :boarding_pass,
  transit_type: :train,
  # ...
}

Class-Shape Differences

Google's class resource has a "title" field with a different name per type. The library reads class_config.event_name and writes it into the right field for the pass type you're creating:

pass_typeClass title fieldLocalized sibling
:event_ticketeventName(already LocalizedString)
:boarding_pass(no class title field — flight details carry the identity)
:store_cardprogramNamelocalizedProgramName
:coupontitlelocalizedTitle
:generic(no class title field)

Two patterns to internalise:

  • :event_ticket carries its title as a native LocalizedString field (eventName). Translations populate translatedValues directly.
  • :store_card and :coupon carry the title as a plain string with a separate localized sibling. The library always writes the plain field and adds the sibling (localizedProgramName, localizedTitle) only when at least one translation matches. See the Localization guide's "plain + localized sibling" section for the full pattern.

The same event_name value drives the class title regardless of type — the library picks the destination field for you:

# Drives "eventName" on an event-ticket class.
WalletPasses.google_save_url(pass_data, google_visual,
  class_config: %{
    id: "summer_2026",
    issuer_name: "Festival Co",
    event_name: "Summer Music Festival",
    pass_type: :event_ticket
  }
)

# Drives "programName" on a loyalty class.
WalletPasses.google_save_url(pass_data, google_visual,
  class_config: %{
    id: "rewards_v1",
    issuer_name: "Coffee Co",
    event_name: "Rewards Program",
    pass_type: :store_card
  }
)

# Drives "title" on an offer class.
WalletPasses.google_save_url(pass_data, google_visual,
  class_config: %{
    id: "summer_promo",
    issuer_name: "Summer Co",
    event_name: "20% Off Everything",
    pass_type: :coupon
  }
)

:boarding_pass and :generic class objects don't take a single "title" field — the library leaves event_name out of the class JSON for those types. Configure identity through type-specific fields on your own (flight details for boarding passes; field arrays for generic).

API Reference

WalletPasses.PassType is a thin lookup module. Every function takes a single pass-type atom and returns a string.

FunctionReturns (for :event_ticket)Used by
apple_style_key/1"eventTicket"Apple pass.json top-level structure key
google_object_type/1"eventTicketObject"Google object resource path
google_class_type/1"eventTicketClass"Google class resource path
google_save_objects_key/1"eventTicketObjects"Save-URL JWT payload key
google_class_suffix/1"event_class"Default class ID suffix when none provided
all/0[:event_ticket, :boarding_pass, :store_card, :coupon, :generic]Validation / enumeration
iex> WalletPasses.PassType.apple_style_key(:boarding_pass)
"boardingPass"

iex> WalletPasses.PassType.google_class_type(:store_card)
"loyaltyClass"

iex> WalletPasses.PassType.google_class_suffix(:coupon)
"offer_class"

iex> WalletPasses.PassType.all()
[:event_ticket, :boarding_pass, :store_card, :coupon, :generic]

You generally don't need to call these directly — they're invoked by the builders. They're public so you can switch on them in your own PassDataProvider or wire them into custom telemetry without hard-coding string literals.