WalletWallet API
Get API Key Docs Pricing 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. A pass can phone home to your server and pull new data — a flight gate change, a new point balance, an updated QR code.
  • They notify. You can push updates to a pass, and the user sees a lock screen notification.
  • They’re location-aware. A pass can surface automatically when the user arrives at a relevant location or at a specific time.
  • They’re scannable. Built-in barcode rendering (QR, PDF417, Aztec, Code128) with no extra libraries.

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 notifications and search (29×29 pt)
├── [email protected]        # Retina variant (58×58 px)
├── logo.png           # Top-left of pass (up to 160×50 pt)
├── [email protected]
├── strip.png          # Optional. Wide banner behind primary fields
├── [email protected]
├── thumbnail.png      # Optional. Small image, top-right (90×90 pt)
├── [email protected]
└── en.lproj/          # Optional. Localization folder
    └── pass.strings

Required files: pass.json, manifest.json, signature, icon.png (plus [email protected] 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.

{
  "primaryFields": [
    {
      "key": "member-name",
      "label": "MEMBER",
      "value": "Jane Cooper"
    }
  ]
}

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.

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
logoTextlogoTextFalls back to title if not set
primaryFieldsprimaryFieldsArray of {label, value}
secondaryFieldssecondaryFieldsArray of {label, value}
headerFieldsheaderFieldsArray of {label, value}
backFieldsbackFieldsArray of {label, value}
BarcodebarcodeValue + barcodeFormatQR, PDF417, Aztec, Code128
ColorscolorPreset or color (hex)Preset: dark, blue, green, red, purple, orange
Strip imagestripURL (Pro)HTTPS URL or base64 data URI
ThumbnailthumbnailURL (Pro)HTTPS URL or base64 data URI

You don’t handle certificates, signing, or image bundling — the API does all of it. Get an API key and you can have a 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.