WalletWallet API
Get API Key Docs Pricing Changelog Log in
Back to Blog

Anatomy of an Apple Wallet Pass: Every Field Explained

See exactly where every field lands on an Apple Wallet pass. Interactive preview + JSON examples for primaryFields, headerFields, barcode, strip images, and more.

2026-04-05 By Alen apple-wallet pkpass passkit pass-design tutorial
Tap any area to see its field name

An Apple Wallet pass looks simple — a colored card with some text and a barcode. But under the surface, every pixel on that card maps to a specific JSON key in the pass.json file inside a .pkpass bundle. If you’re building wallet passes via an API or with code, understanding this mapping is the difference between a pass that looks polished and one where fields collide, text truncates, or images disappear.

This guide walks through every field on a wallet pass, front and back, with the exact JSON you’d write and where it renders visually. Try editing the fields in the interactive preview below to see how each one maps to the pass.

What is a wallet pass?

A wallet pass is a digital card that lives in Apple Wallet (or Google Wallet). Boarding passes, loyalty cards, event tickets, coupons, membership cards — anything you’d carry in a physical wallet can become a pass.

From a technical perspective, a wallet pass is a .pkpass file. It’s a signed ZIP archive containing a pass.json file (the data), image assets, a manifest, and a cryptographic signature. When a user taps “Add to Apple Wallet,” iOS verifies the signature, reads the JSON, and renders the pass.

What makes wallet passes more useful than a PDF or a screenshot:

  • They update after install. A pass that points at a web service can be refreshed remotely — change a field on the server, every device with the pass installed pulls the new content within seconds. A flight gate change, a new point balance, a refreshed QR code.
  • They notify. When content changes, the device wakes via an Apple Push Notification and shows a banner on the lock screen — with text you control via the changeMessage template (“You earned 50 points”).
  • They’re location-aware. A pass can carry up to 10 location triggers; Wallet surfaces it on the lock screen when the device is near one of them.
  • They’re scannable. Built-in barcode rendering (QR, PDF417, Aztec, Code128) with no extra libraries.

We’ll get into the JSON for each of those further down — webServiceURL + authenticationToken for updates, locations for lock-screen relevance, changeMessage on individual fields for the banner text.

Users add passes by tapping a link, opening an email attachment, scanning a QR code, or through an in-app button. The file’s MIME type (application/vnd.apple.pkpass) tells iOS to open it in Wallet.

The pkpass file format

Rename any .pkpass file to .zip and unzip it. Inside you’ll find:

MyPass.pkpass/
├── pass.json          # All pass data — fields, colors, barcode, metadata
├── manifest.json      # SHA-1 hash of every other file (integrity check)
├── signature           # PKCS#7 detached signature over manifest.json
├── icon.png           # Required. Shown in iOS notifications + search (29×29 pt)
├── icon@2x.png        # Retina variant (58×58 px)
├── logo.png           # Top-left of pass face (up to 160×50 pt)
├── logo@2x.png
├── strip.png          # Optional. Wide banner behind primary fields (375×98–144 pt)
├── strip@2x.png
├── thumbnail.png      # Optional. Small image, top-right of pass face (90×90 pt)
├── thumbnail@2x.png
└── en.lproj/          # Optional. Localization folder
    └── pass.strings

A common point of confusion: icon.png and logo.png are different things.

  • icon.png is the small square shown in the iOS lock-screen notification banner when the pass updates, and in Wallet search results. It’s the pass’s “app icon.” 29×29 pt at @1x.
  • logo.png renders on the face of the pass itself, top-left. It’s the brand wordmark — Bayroast Coffee’s logo, an airline’s logo, etc.
  • thumbnail.png is a square slot on the pass face, top-right — typically a customer photo, product image, or secondary brand mark.
  • strip.png is a wide banner that spans the top of the pass face on storeCard, coupon, and eventTicket styles.

If you supply only logo.png, the lock-screen notification falls back to a generic placeholder icon. To brand the notification too, supply icon.png (or iconURL in the API). To get a face image visible at full color on the pass face, use thumbnailURLlogoURL renders the brand mark only.

Required files: pass.json, manifest.json, signature, icon.png (plus icon@2x.png for Retina). Everything else is optional.

manifest.json is a dictionary mapping every filename to its SHA-1 hash. iOS uses this to verify that nothing in the archive has been tampered with.

signature is a PKCS#7 detached signature created with your Apple Developer pass signing certificate and the Apple WWDR (Worldwide Developer Relations) intermediate certificate. This is the part that tells iOS “this pass was created by an authorized developer.” It’s also the part that burns the most time if you’re building the signing pipeline yourself.

Image variants: iOS picks the highest-resolution variant that fits the device. Provide @2x assets at minimum — nearly all current iPhones are Retina. The @3x suffix is supported for Plus/Max-sized phones.

The maximum recommended size for a .pkpass file is around 1.5 MB. Keep images optimized — the pass needs to download quickly when a user taps “Add to Apple Wallet.”

Pass styles

Every pass has a style that determines its visual layout and which image slots are available. You set the style in pass.json by using the style name as a top-level key:

{
  "formatVersion": 1,
  "generic": {
    "primaryFields": [...],
    "secondaryFields": [...]
  }
}

The five styles:

StyleJSON keyBest forSupported images
GenericgenericMembership cards, IDs, any general-purpose passlogo, icon, thumbnail
Store CardstoreCardLoyalty cards, gift cards, points cardslogo, icon, strip
Event TicketeventTicketConcerts, movies, sports, conferenceslogo, icon, strip OR (background + thumbnail)
CouponcouponDiscounts, special offers, promo codeslogo, icon, strip
Boarding PassboardingPassFlights, trains, buses, ferrieslogo, icon, footer

Here’s what each style looks like — notice how the field layout and visual signature differ:

Generic pass example
generic
Store card pass example
storeCard
Event ticket pass example
eventTicket
Coupon pass example
coupon
Boarding pass example
boardingPass

Images from Apple Developer Documentation.

Each style has a distinct visual signature — boarding passes have a transit icon between origin and destination, coupons have a perforated top edge, event tickets have a small notch. You can’t change these details; they’re baked into iOS.

Choosing a style: Pick the one closest to your use case. If nothing fits, use generic. You can’t change the style after issuing a pass (it’s tied to the passTypeIdentifier), so decide up front.

A note on images by style: Event tickets are the most flexible — they support strip, background, AND thumbnail images, though if you use a strip, you can’t also use a background or thumbnail. Generic passes only support thumbnails (no strip). Store cards and coupons only support strip (no thumbnail).

Front of the pass — field by field

Here’s where the abstract JSON keys become real pixels on screen. Each field area has a specific location, a specific capacity, and specific truncation behavior.

A quick orientation: field arrays in pass.json contain objects with key, label, and value. The key is an internal identifier (not displayed). The label appears as small uppercase text above the value. Any field can also carry an optional changeMessage — a template iOS substitutes onto the lock-screen banner when this field’s value changes during an update:

{
  "primaryFields": [
    {
      "key": "member-name",
      "label": "MEMBER",
      "value": "Jane Cooper"
    }
  ],
  "secondaryFields": [
    {
      "key": "points",
      "label": "POINTS",
      "value": "1250",
      "changeMessage": "You now have %@ points"
    }
  ]
}

If points later goes from 1250 to 1500, the lock-screen banner reads “You now have 1500 points”. Without changeMessage, iOS falls back to the generic “Pass Updated” text. %@ is the substitution token for the new value.

Logo and logoText

Position: Top-left of the pass.

The logo image (logo.png) renders in the top-left corner, followed by logoText — a string that appears next to the logo. Together they identify who issued the pass.

{
  "logoText": "Bayroast Coffee",
  ...
}

Sizing: The logo image should be up to 160×50 points (@1x). At @2x, that’s 320×100 pixels. Keep it simple — a wordmark or icon that’s legible at small sizes.

If you provide logoText but no logo image, the text still renders. If you provide neither, the top-left is blank. If both are set, they appear side by side.

Watch the length of logoText — if it’s too long, it collides with header fields on the right. Around 15–20 characters is a safe maximum, depending on the font width and how many header fields you have.

Header fields

Position: Top-right of the pass.
JSON key: headerFields

{
  "headerFields": [
    {
      "key": "balance",
      "label": "POINTS",
      "value": "1,250"
    }
  ]
}

Header fields are special because they’re the only fields visible when the pass is stacked in the Wallet app. When a user has multiple passes, they see a stack of cards with just the top sliver — logo, logoText, and header fields. This makes header fields the most valuable real estate on the entire pass.

Use them for the single most important dynamic value: a point balance, a flight status, an event date, a seat number.

You can have up to 3 header fields, but in practice 1–2 is ideal. Each one takes horizontal space, and they share the top-right with the thumbnail image on generic and event ticket passes. The label always shows above the value in small text.

Primary fields

Position: The large, bold row in the center of the pass.
JSON key: primaryFields

{
  "primaryFields": [
    {
      "key": "event-name",
      "label": "EVENT",
      "value": "Summer Music Festival"
    }
  ]
}

Primary fields display the most important information on the pass — the thing the user needs to see at a glance. The value renders in a large, prominent font.

Capacity: Most pass styles support 1 primary field. Boarding passes are the exception — they support 2, rendered side by side with a transit icon (plane, train, bus, ferry) between them.

Truncation: Primary field values truncate at roughly 20–25 characters on most devices, depending on the font and device width. iOS does not wrap text — if there is too much text, fields may not be displayed at all. Keep primary values short and impactful.

Secondary fields

Position: Below the primary fields.
JSON key: secondaryFields

{
  "secondaryFields": [
    {
      "key": "member-since",
      "label": "MEMBER SINCE",
      "value": "2024"
    },
    {
      "key": "tier",
      "label": "TIER",
      "value": "Gold"
    }
  ]
}

Secondary fields show supporting information. Multiple fields sit side by side, dividing the available width equally. Labels always appear above their values.

Capacity: Up to 4 secondary fields on most styles. However, on coupons, store cards, and generic passes with a square barcode (QR or Aztec), the secondary and auxiliary fields share a combined limit of 4 total. If you have 2 secondary and 3 auxiliary, only 4 will render.

When field text is too long, iOS truncates individual values rather than dropping entire fields — but it’s better to keep values concise.

Auxiliary fields

Position: Below or alongside secondary fields, depending on pass style.
JSON key: auxiliaryFields

{
  "auxiliaryFields": [
    {
      "key": "gate",
      "label": "GATE",
      "value": "B12"
    }
  ]
}

Auxiliary fields behave similarly to secondary fields but are treated as a separate row. On boarding passes, up to 5 auxiliary fields can appear (more than other styles). On other styles, they share the combined 4-field limit with secondary fields.

The visual difference between secondary and auxiliary fields is subtle — they use the same font size and styling. The distinction is mainly about layout order and how Apple Watch renders them (auxiliary fields appear after secondary fields, each on their own line).

Strip image and thumbnail

These are the two image slots that appear on the front of a pass (besides the logo). Which one you get depends on the pass style.

Strip image (strip.png): A wide banner that sits behind the primary fields. Supported on store cards, coupons, and event tickets.

  • Recommended size: 375×123 points (@1x) for most styles — but 375×98 pt for event tickets and 375×144 pt for coupons and gift cards
  • The strip spans the full width of the pass
  • Text renders on top of the strip — make sure your foregroundColor contrasts with the image
  • On Apple Watch, the strip image is not displayed

What happens to the layout when you add a strip: The strip image becomes the background of the primary fields area. The primary field labels and values render directly on top of the image. This means busy or bright images can make your text unreadable — use a darker image, or overlay it with your backgroundColor at reduced opacity. Because of this, secondary fields become more important in strip layouts — they sit below the strip on a solid background and are always legible. If you have information that must be readable at a glance, put it in secondary fields rather than primary when using a strip.

Thumbnail (thumbnail.png): A small image in the top-right area, next to the header fields. Supported on generic passes and event tickets (when not using a strip).

  • Allotted space: 90×90 points (@1x), so 180×180 pixels at @2x. Aspect ratio can range from 2:3 to 3:2
  • Good use cases: user profile photo, product image, company logo mark
  • Renders with rounded corners automatically

You can’t use both on the same pass. On event tickets, if you provide a strip image, the thumbnail and background are ignored. Choose based on your content: strip for branded visual impact, thumbnail for personalization.

Barcode

Position: Bottom of the pass, centered.
JSON key: barcodes (array)

{
  "barcodes": [
    {
      "format": "PKBarcodeFormatQR",
      "message": "MEMBER-12345-GOLD",
      "messageEncoding": "iso-8859-1",
      "altText": "MEMBER-12345-GOLD"
    }
  ]
}

Four barcode formats are supported:

FormatJSON valueBest for
QRPKBarcodeFormatQRMost use cases. Compact, fast to scan
PDF417PKBarcodeFormatPDF417Legacy systems, some event venues
AztecPKBarcodeFormatAztecTransit systems (used by many airlines)
Code128PKBarcodeFormatCode128Retail POS scanners, simple numeric codes

Why is barcodes an array? It’s a fallback chain. iOS renders the first format it supports. This matters because the older barcode key (singular) only supported one format and was deprecated in iOS 9. Always use the barcodes array. You can include the legacy barcode key alongside it for backward compatibility with very old devices.

altText appears below the barcode as a human-readable string — useful when a scanner fails and someone needs to type the code manually.

A pass can have no barcode — it’s optional. Membership cards that rely on NFC or visual verification don’t need one.

NFC: Contactless scanning uses a separate top-level nfc key, not the barcodes array. NFC passes require Apple’s explicit approval and are typically reserved for payment and transit integrations.

Back of the pass

Tap the info button (ⓘ) on any pass to flip it over. The back is a scrollable list of fields — no layout constraints, no truncation limits.

{
  "backFields": [
    {
      "key": "terms",
      "label": "TERMS & CONDITIONS",
      "value": "This pass is valid for one year from the date of issue. Points expire 90 days after last activity. See example.com/terms for full details."
    },
    {
      "key": "support",
      "label": "SUPPORT",
      "value": "https://example.com/support"
    },
    {
      "key": "phone",
      "label": "CUSTOMER SERVICE",
      "value": "+1 (555) 123-4567"
    }
  ]
}

Auto-linking: iOS automatically detects and makes tappable:

  • URLs — opens Safari
  • Phone numbers — opens Phone
  • Email addresses — opens Mail
  • Street addresses — opens Maps

No HTML or markdown is supported. It’s plain text with smart detection.

No practical limit on the number of back fields. Use the back for everything that doesn’t need to be visible at a glance: terms and conditions, account IDs, support links, associated app link, detailed instructions.

The back is not available on Apple Watch — keep critical information on the front.

How passes update after install

A pass is not a static artifact — the same serial can carry different content over time. Apple Wallet’s update model is a wake-and-poll loop, not a payload push.

The pieces in pass.json that make this possible:

{
  "passTypeIdentifier": "pass.com.example.loyalty",
  "serialNumber": "8f4c3a2e-…",
  "webServiceURL": "https://api.example.com",
  "authenticationToken": "32-hex-chars-or-more"
}
  • serialNumber — your handle for “this specific pass.” It’s how you refer to the pass when you want to update it.
  • webServiceURL — the base URL Wallet polls for updates. Devices append paths like /v1/devices/.../registrations/... and /v1/passes/{type}/{serial} to it.
  • authenticationToken — opaque shared secret baked into the pass. Devices send it back when they fetch updates so your server knows the request comes from a legitimately-installed pass.

What happens in practice:

  1. Install. The user adds the pass. iOS reads webServiceURL from pass.json and immediately calls POST /v1/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}/{serialNumber} to register, sending its APNs push token along.
  2. Issuer changes something. Your backend updates the pass content (e.g., the points balance) and bumps a “last modified” timestamp on the server.
  3. Push notification. Your server fans out an APNs push to every registered device. The push payload is empty — it’s just a wake-up signal. The lock-screen text doesn’t come from the push body; it comes from any changeMessage field that changed.
  4. Device polls. iOS calls GET /v1/devices/.../registrations/...?passesUpdatedSince=<tag> to ask “what’s changed since the last time you told me?” Your server returns the list of changed serial numbers.
  5. Device fetches new bytes. iOS calls GET /v1/passes/{type}/{serial} (with Authorization: ApplePass <authenticationToken>) and pulls a fresh .pkpass file. Wallet replaces the installed pass in place.

The whole loop takes ~1–3 seconds end-to-end on a real iPhone.

You don’t have to implement any of this yourself — most Wallet pass APIs (this one included) handle the registrations table, the APNs JWT signing, and the polling endpoints. You just call PUT /api/pkpass/{serial} with the new content and the loop runs.

Location triggers

Wallet can surface a pass on the lock screen without a push, when the device is physically near a coordinate you set:

{
  "locations": [
    {
      "latitude": 37.331741,
      "longitude": -122.030333,
      "relevantText": "Welcome to Apple Park"
    }
  ]
}

Up to 10 locations per pass. iOS uses its significant-location-change service — meaning the surfacing isn’t instant the moment you cross the geofence; it’s tied to the device’s normal location updates and can lag a minute or two. Good for: a coffee loyalty pass surfacing at the shop’s coordinates, a stadium ticket at the venue, an event coupon at the store.

The optional relevantText (≤128 chars) is what the lock-screen banner reads when the surfacing fires.

Colors and appearance

Three color keys control how a pass looks:

{
  "backgroundColor": "rgb(30, 64, 175)",
  "foregroundColor": "rgb(255, 255, 255)",
  "labelColor": "rgb(191, 219, 254)"
}
KeyControlsFormat
backgroundColorPass background colorrgb(R, G, B)
foregroundColorValue text colorrgb(R, G, B)
labelColorLabel text color (the small text above values)rgb(R, G, B)

If you don’t set colors, iOS attempts to derive them from the logo or strip image — it samples the dominant color and picks complementary text colors. The result is often acceptable, but unpredictable. Set colors explicitly for consistent branding.

Contrast adjustment: iOS may adjust your colors if there isn’t enough contrast between foregroundColor and backgroundColor. In practice, this means your pass should always be readable — but test with your exact color combination to avoid surprises.

Common mistakes:

  • Setting labelColor the same as foregroundColor — labels and values blend together
  • Dark text on a dark background (forgetting to change foregroundColor when darkening the background)
  • Not testing on the lock screen, where the pass renders as a notification banner with different ambient lighting

Creating a wallet pass with an API

Everything above — the fields, the images, the barcode, the colors — maps directly to a single API call. Here’s a pass that uses most of the fields we’ve covered:

curl -X POST https://api.walletwallet.dev/api/pkpass \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "barcodeValue": "MEMBER-12345",
    "barcodeFormat": "QR",
    "logoText": "Bayroast Coffee",
    "colorPreset": "dark",
    "primaryFields": [
      { "label": "MEMBER", "value": "Jane Cooper" }
    ],
    "secondaryFields": [
      { "label": "TIER", "value": "Gold" },
      { "label": "SINCE", "value": "2024" }
    ],
    "headerFields": [
      { "label": "POINTS", "value": "1,250" }
    ],
    "backFields": [
      { "label": "TERMS", "value": "Points expire after 90 days of inactivity." },
      { "label": "SUPPORT", "value": "https://example.com/help" }
    ]
  }' \
  --output pass.pkpass

The response is a signed .pkpass file you can send to users via email, link, or an “Add to Apple Wallet” button.

Field mapping from this article to the API:

Pass fieldAPI parameterNotes
organizationNameorganizationNameShown under the pass title in Wallet. Max 64 chars.
logoTextlogoTextFalls back to title if not set
primaryFieldsprimaryFieldsArray of {label, value, changeMessage?}
secondaryFieldssecondaryFieldsArray of {label, value, changeMessage?}
headerFieldsheaderFieldsArray of {label, value, changeMessage?}
backFieldsbackFieldsArray of {label, value, changeMessage?}
BarcodebarcodeValue + barcodeFormatQR, PDF417, Aztec, Code128
ColorscolorPreset or color (hex)Preset: dark, blue, green, red, purple, orange
Logo (pass face)logoURL (Pro)HTTPS URL or base64 data URI
Thumbnail (pass face)thumbnailURL (Pro)HTTPS URL or base64 data URI
Strip (banner)stripURL (Pro)HTTPS URL or base64 data URI. Switches layout to storeCard.
Icon (notifications)iconURL (Pro)HTTPS URL or base64 data URI. Replaces the placeholder used in lock-screen banners.
LocationslocationsUp to 10 {latitude, longitude, altitude?, relevantText?} entries.

POST returns the pass binary plus an X-Serial-Number response header — that’s your handle for pushing updates later. To update the pass content on every installed device, PUT the same body shape against /{serial}:

curl -X PUT https://api.walletwallet.dev/api/pkpass/$SERIAL \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "barcodeValue": "MEMBER-12345",
    "barcodeFormat": "QR",
    "logoText": "Bayroast Coffee",
    "secondaryFields": [
      {
        "label": "POINTS",
        "value": "1500",
        "changeMessage": "You earned %@ points"
      }
    ]
  }'

The push fires automatically; every device with the pass installed banners and refreshes in place. Identical bodies are a no-op — no push, no quota.

You don’t handle certificates, signing, image bundling, registrations, or APNs JWT signing — the API does all of it. Get an API key and you can have an updatable pass in someone’s wallet in under a minute.

Written by Alen Todorov, founder of WalletWallet API — the tech behind the consumer product WalletWallet, which was featured on Product Hunt, hit the Reddit frontpage, and has generated over 100,000 passes.