npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nikpnevmatikos/pass-generator

v0.1.0

Published

Create passes for Apple Wallet (.pkpass) and Google Wallet from one wallet-agnostic ticket model.

Readme

@nikpnevmatikos/pass-generator

Create passes for Apple Wallet (.pkpass) and Google Wallet from one wallet-agnostic model. Describe a pass once — issue it to either wallet. Supports event tickets, boarding passes, coupons, store/loyalty cards, and generic passes.

It's an embeddable TypeScript library: you import a function and get a pass. No server required.

import { createApplePass, createGoogleWalletSaveUrl } from "@nikpnevmatikos/pass-generator";

| Wallet | You call | You get | | --- | --- | --- | | Apple | createApplePass(ticket, …) | a signed .pkpass Buffer — email or serve it | | Google | createGoogleWalletSaveUrl(ticket, …) | a "Save to Google Wallet" URL (a signed JWT) |

Both take the same WalletPass — a union of pass kinds selected by a kind field (omit it for an event ticket, so existing code keeps working).


Install

npm install @nikpnevmatikos/pass-generator

Requires Node ≥ 18. Developing on the repo itself instead? npm install && npm run build.


Quick start

Apple Wallet

import { createApplePass, loadCertificatesFromDir, type WalletTicket } from "@nikpnevmatikos/pass-generator";
import { writeFileSync } from "node:fs";

const ticket: WalletTicket = {
  serialNumber: "TCKT-2026-0001",          // unique per pass
  description: "Concert ticket",
  organizationName: "Acme Tickets",
  eventName: "Summer Synth Festival",
  venueName: "Technopolis, Athens",
  date: new Date("2026-07-18T20:00:00+03:00"),
  holderName: "Alex Taylor",
  ticketType: "VIP",
  seat: { section: "A", row: "3", seat: "12" },
  barcode: { format: "qr", message: "TCKT-2026-0001|verify-token" },
  colors: { background: "#1c1c28", foreground: "#ffffff", label: "#b6b6c8" },
  images: { icon: "./assets/icon.png", logo: "./assets/logo.png" },
};

const pkpass = await createApplePass(ticket, {
  identity: { passTypeIdentifier: "pass.com.example.ticket", teamIdentifier: "ABCDE12345" },
  certificates: loadCertificatesFromDir("./certs"),
});
writeFileSync("ticket.pkpass", pkpass); // application/vnd.apple.pkpass

Google Wallet

import { createGoogleWalletSaveUrl, loadGoogleCredentials } from "@nikpnevmatikos/pass-generator";

const url = createGoogleWalletSaveUrl(ticket, {
  credentials: loadGoogleCredentials({
    serviceAccountPath: "./certs/google-service-account.json",
    issuerId: "3388000000XXXXXXXXX", // numeric Issuer ID
  }),
});
// -> https://pay.google.com/gp/v/save/<jwt>  — put it behind an "Add to Google Wallet" button

The same ticket drives both. Google-specific branding (logoUrl, heroImageUrl, homepageUrl) is optional — see the model below.


The WalletPass model

WalletPass is a discriminated union — pick a kind and fill in its fields. A pass without a kind is treated as an eventTicket, so existing WalletTicket code keeps working (WalletTicket is an alias for the event-ticket variant).

type PassKind = "eventTicket" | "boardingPass" | "coupon" | "storeCard" | "generic";

Shared fields — every kind has these; only a few are required:

| Field | Required | Notes | | --- | --- | --- | | serialNumber | ✅ | Unique id for this pass | | description | ✅ | Short text, also used for accessibility (Apple) | | organizationName | ✅ | Your brand / issuer name | | kind | — | Pass type; omit for eventTicket. See Pass types for kind-specific fields | | barcode | ✅ | { format: "qr" \| "pdf417" \| "aztec" \| "code128", message, altText? } | | images.icon | ✅ for Apple | PNG path or Buffer; @2x/@3x via { "1x":…, "2x":… } | | date, expirationDate | — | date drives Apple lock-screen relevance / Google validity start | | logoText, colors | — | Colours accept hex (#1c1c28) or rgb(28, 28, 40) | | backFields | — | { label, value }[] — back of the Apple pass / Google text rows | | locations | — | { latitude, longitude, relevantText? }[] (Apple) | | logoUrl, heroImageUrl, homepageUrl | — | Google only — hosted HTTPS URLs (logo, banner, website). Apple uses embedded images. | | webServiceURL + authenticationToken | — | Advanced — Apple push updates | | userInfo | — | Arbitrary data round-tripped in the Apple pass |

Why the split for images? Apple embeds PNGs inside the signed .pkpass; Google references images by public URL. So Apple reads images (paths/Buffers) and Google reads logoUrl/heroImageUrl.

Need the raw Apple pass.json without signing (for previews/tests)? Call buildApplePassJson(pass, identity).


Pass types

Set kind to choose the type. Each maps to an Apple style and a Google pass type:

| kind | Apple style | Google type | Kind-specific fields (✅ = required) | | --- | --- | --- | --- | | eventTicket (default) | eventTicket | Event ticket | eventName ✅, venueName, doorsOpen, holderName, seat, ticketType | | boardingPass | boardingPass | Transit | transitType ✅, origin ✅, destination ✅, passengerName, boardingTime, gate, seat, carrierName, tripNumber | | coupon | coupon | Offer | offerName ✅, provider, terms, promoCode | | storeCard | storeCard | Loyalty | programName ✅, memberName, memberId, balance, tier | | generic | generic | Generic | title, subtitle, headerFields, primaryFields, secondaryFields, auxiliaryFields |

  • transitType is "air" \| "train" \| "bus" \| "boat" \| "generic".
  • origin / destination are { code?, name }, e.g. { code: "ATH", name: "Athens" }.
  • seat is { section?, row?, seat?, admissionLevel? }.
  • headerFields / primaryFields / … are { label, value }[].

Boarding passes map to Google Transit, not Flight. Flight requires airline-only fields (carrier code, IATA airport codes, scheduled datetimes) a train/bus can't supply. Two consequences: Google Transit has no "air" bucket, so transitType: "air" shows as OTHER; and Google requires a logo on transit passes, so set logoUrl for boarding passes. (Full airline Flight support is on the roadmap.)

Google requires a logo for boardingPass and storeCard. Both transitClass and loyaltyClass mandate a logo, so createGoogleWalletSaveUrl throws if logoUrl is missing for those kinds. (Coupons, event tickets, and generic passes don't need one.) Apple is unaffected — it uses the embedded images instead.

Examples

Every kind goes through the same createApplePass / createGoogleWalletSaveUrl shown above — only the model changes.

import type { WalletPass } from "@nikpnevmatikos/pass-generator";

const boarding: WalletPass = {
  kind: "boardingPass",
  serialNumber: "BP-1", description: "Boarding pass", organizationName: "Acme Air",
  transitType: "air",
  origin: { code: "ATH", name: "Athens" },
  destination: { code: "LHR", name: "London Heathrow" },
  passengerName: "Alex Taylor", tripNumber: "AA 178", gate: "B12", seat: { seat: "14C" },
  barcode: { format: "pdf417", message: "BP-1|token" },
  images: { icon: "./assets/icon.png" },
  logoUrl: "https://example.com/logo.png", // required for Google transit
};

const coupon: WalletPass = {
  kind: "coupon",
  serialNumber: "CPN-1", description: "20% off", organizationName: "Acme Coffee",
  offerName: "20% off any coffee", promoCode: "BREW20", terms: "One per customer.",
  barcode: { format: "qr", message: "CPN-1" },
  images: { icon: "./assets/icon.png" },
};

const card: WalletPass = {
  kind: "storeCard",
  serialNumber: "LOY-1", description: "Rewards card", organizationName: "Acme Rewards",
  programName: "Acme Rewards", memberId: "AR-99823", balance: "1,250 pts", tier: "Gold",
  barcode: { format: "qr", message: "LOY-1" },
  images: { icon: "./assets/icon.png" },
};

const generic: WalletPass = {
  kind: "generic",
  serialNumber: "GEN-1", description: "Gym locker", organizationName: "Acme Gym",
  title: "Locker 42", subtitle: "Members' area",
  primaryFields: [{ label: "Locker", value: "42" }],
  barcode: { format: "qr", message: "GEN-1" },
  images: { icon: "./assets/icon.png" },
};

See examples/pass-types.ts for a runnable version of all four.


Apple Wallet

Prerequisites

  1. Apple Developer Program membership ($99/year).
  2. A Pass Type ID and its certificate → see below.
  3. Node ≥ 18.

Creating your certificate (Windows + openssl)

openssl ships with Git for Windows. Run these from the project root.

1 — Register the Pass Type ID. developer.apple.com/accountCertificates, Identifiers & ProfilesIdentifiers → + → Pass Type IDs → pick an id like pass.com.you.ticket. Note it, and your Team ID (10 chars, Membership page).

2 — Generate a private key + CSR.

openssl genrsa -out certs/pass.key 2048
openssl req -new -key certs/pass.key -out certs/pass.csr -subj "/CN=My Pass/[email protected]/C=US"

3 — Create the certificate. Open your Pass Type ID → Create Certificate → upload certs/pass.csr → download pass.cer.

4 — Convert to PEM.

openssl x509 -inform DER -in certs/pass.cer -out certs/signerCert.pem
# Apple WWDR intermediate (G4) from https://www.apple.com/certificateauthority/ :
openssl x509 -inform DER -in certs/AppleWWDRCAG4.cer -out certs/wwdr.pem

End state — loadCertificatesFromDir("./certs") expects exactly this (folder is gitignored):

certs/
├── signerCert.pem   ← Pass Type ID certificate
├── pass.key         ← private key
└── wwdr.pem         ← Apple WWDR (G4) intermediate

If your key is encrypted, set APPLE_PASS_KEY_PASSPHRASE.

Distributing a .pkpass

Deliver it with the right MIME type and iOS shows an "Add to Wallet" sheet:

  • Email — attach the .pkpass.
  • Download linkContent-Type: application/vnd.apple.pkpass.
res.setHeader("Content-Type", "application/vnd.apple.pkpass");
res.setHeader("Content-Disposition", 'attachment; filename="ticket.pkpass"');
res.send(pkpass);

Google Wallet

No file to sign: you define a class (per-event template) and an object (the ticket), embed them in a JWT signed with your service-account key, and hand the user a Save link. Free to test.

Prerequisites — one-time setup

You need one thing from each of two Google consoles.

A. Google Cloud Console → the service-account key (console.cloud.google.com)

  1. Pick or create a project.
  2. Enable the Wallet API: console.cloud.google.com/apis/library/walletobjects.googleapis.comEnable.
  3. Create a service account: console.cloud.google.com/iam-admin/serviceaccounts/create → name it → Done. Copy its email (…@project.iam.gserviceaccount.com).
  4. Download a JSON key: open the service account → KEYS → ADD KEY → Create new key → JSON. 🔒 Secret — treat it like your Apple pass.key.

B. Google Pay & Wallet Console → the Issuer ID + authorization (pay.google.com/business/console)

  1. Sign in (create an issuer account if you don't have one).
  2. Find your Issuer ID in the Google Wallet API section — it's a long number (e.g. 3388000000022…). ⚠️ Not the BCR2DN… Google Pay merchant ID — that's a different identifier and will fail.
  3. Authorize the service account: Users → Invite a user → paste the service-account email from step 3 → access level DeveloperInvite. (Must be done here, not in Cloud IAM.)
  4. Add a test account: new issuers are in demo mode, so passes only save to allow-listed accounts. Add the Google account you'll test with (Users, or the "test accounts" list under the Wallet API dashboard).

Usage

const credentials = loadGoogleCredentials({
  serviceAccountPath: "./certs/google-service-account.json",
  issuerId: "3388000000XXXXXXXXX",   // or set GOOGLE_WALLET_ISSUER_ID
});
const url = createGoogleWalletSaveUrl(ticket, { credentials /*, classSuffix, origins */ });

Add branding via ticket.logoUrl / heroImageUrl / homepageUrl (hosted HTTPS URLs) and colors.background.

Testing

Open the Save URL signed into your allow-listed Google account (Android Chrome, or desktop browser → syncs to your phone). Demo-mode passes show a [TEST ONLY] banner — that's expected until your issuer is approved for production.

Common errors

  • "Service account … did not have edit access on the issuer" → step 7 (invite as Developer), or the Issuer ID doesn't match the issuer where the account is authorized.
  • Wrong Issuer ID → make sure it's the numeric one from step 6, not the BCR2DN… merchant ID.

Running the example

# Apple (optional — falls back to an unsigned preview if no certs):
#   $env:APPLE_PASS_TYPE_ID="pass.com.you.ticket"; $env:APPLE_TEAM_ID="ABCDE12345"
# Google (optional — needs certs/google-service-account.json):
#   $env:GOOGLE_WALLET_ISSUER_ID="3388000000XXXXXXXXX"
#   $env:PASS_LOGO_URL="https://…/logo.png"   # optional branding
npm run example        # event ticket (both wallets)
npm run example:types  # boarding pass, coupon, store card, generic
  • Apple: writes a signed .pkpass per pass (or an unsigned <serial>.preview.json if no certs).
  • Google: writes a <serial>.google-save-url.txt per pass (if a service account + issuer id are present).

The examples generate their own placeholder PNGs, so they run with zero asset files.

Run the pure mapping tests (no certs or network needed) with npm test.


Apple vs Google at a glance

| | Apple | Google | | --- | --- | --- | | Output | signed .pkpass file | "Save" URL (JWT) | | Auth | Pass Type ID certificate | service-account key | | Images | embedded PNGs | hosted URLs | | Colours | background + foreground + label | single hexBackgroundColor | | Cost to test | $99/yr (cert required) | free (demo mode) |


Project structure

src/
├── index.ts              # public API
├── model/ticket.ts       # WalletPass union (+ WalletTicket alias)
├── apple/
│   ├── builder.ts        # pass -> signed .pkpass (uses passkit-generator)
│   ├── pass-json.ts      # pass -> Apple pass.json (per-kind fields & props)
│   ├── images.ts         # image slots -> { filename: Buffer }
│   └── certificates.ts   # load + validate signing material
└── google/
    ├── builder.ts        # pass -> signed "Save to Google Wallet" URL
    ├── pass-object.ts    # pass -> per-kind Class + Object (event/transit/offer/loyalty/generic)
    └── credentials.ts    # load issuer id + service-account key
examples/
├── event-ticket.ts       # runnable demo — event ticket (both wallets)
└── pass-types.ts          # runnable demo — boarding / coupon / storeCard / generic
test/mappings.test.ts     # pure mapping tests (no certs/network)
certs/                     # your signing material (gitignored)

Security

  • Never commit certs/. It holds your Apple private key and Google service-account key — anyone with them can issue passes as you. It's gitignored; keep it that way and load secrets from a secret manager in production.
  • Treat the Apple authenticationToken (if you use push updates) as a secret too.

License

MIT © Nik Pnevmatikos