# Pass Types
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:
- [Getting Started](getting-started.md) for a full first-pass walkthrough.
- [Apple Wallet](apple-wallet.md) for the Apple style-key behaviour.
- [Google Wallet](google-wallet.md) for class/object semantics.
## 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`:
```elixir
%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_type` | Apple style key | Google class | Google object | Save-URL JWT key | Class ID suffix |
|-------------------|-----------------|--------------------|---------------------|------------------------|-----------------|
| `:event_ticket` | `eventTicket` | `eventTicketClass` | `eventTicketObject` | `eventTicketObjects` | `event_class` |
| `:boarding_pass` | `boardingPass` | `flightClass` | `flightObject` | `flightObjects` | `flight_class` |
| `:store_card` | `storeCard` | `loyaltyClass` | `loyaltyObject` | `loyaltyObjects` | `loyalty_class` |
| `:coupon` | `coupon` | `offerClass` | `offerObject` | `offerObjects` | `offer_class` |
| `:generic` | `generic` | `genericClass` | `genericObject` | `genericObjects` | `generic_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_name` | Google: `ticketHolderName` | Google: `passengerName` | Google: `accountName` | not mapped | Google: `header.defaultValue` (top-line text) |
| `transit_type` | ignored | required (defaults `:air`) | ignored | ignored | ignored |
| `event_name` | suggested for class title | suggested for class title | suggested for class title | suggested for class title | suggested 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](nfc.md) 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` atom | Apple `transitType` value |
|---------------------|---------------------------|
| `:air` | `PKTransitTypeAir` |
| `:boat` | `PKTransitTypeBoat` |
| `:bus` | `PKTransitTypeBus` |
| `:train` | `PKTransitTypeTrain` |
| `:generic` | `PKTransitTypeGeneric` |
`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.
```elixir
%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_type` | Class title field | Localized sibling |
|-------------------|-------------------|----------------------------|
| `:event_ticket` | `eventName` | (already `LocalizedString`) |
| `:boarding_pass` | (no class title field — flight details carry the identity) | — |
| `:store_card` | `programName` | `localizedProgramName` |
| `:coupon` | `title` | `localizedTitle` |
| `: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](localization.md) 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:
```elixir
# 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.
| Function | Returns (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 |
```elixir
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.