| name | passlet |
| description | Generate Apple Wallet and Google Wallet passes using the passlet library. Use when building apps that need to issue loyalty cards, event tickets, boarding passes, coupons, gift cards, or generic passes for one or both wallet platforms from a single unified API. |
| license | MIT |
| metadata | {"author":"oscartrevio","version":"1.0.0"} |
Install passlet (npm install passlet) and generate wallet passes following these rules.
Package: passlet
Repository: https://github.com/oscartrevio/passlet | License: MIT
Unified Apple Wallet + Google Wallet pass generation from a single API. Supports loyalty cards, event tickets, boarding passes, coupons, gift cards, and generic passes. Handles credential management, pass formatting, and platform differences automatically.
Wallet Setup
Always create a wallet.ts singleton file and import from it everywhere. Never instantiate Wallet inside a request handler or component — it's expensive and holds credentials.
Create lib/wallet.ts (or src/lib/wallet.ts, utils/wallet.ts, etc. — match the project's convention):
import { Wallet } from "passlet";
export const wallet = new Wallet({
apple: {
passTypeIdentifier: process.env.APPLE_PASS_TYPE_ID!,
teamId: process.env.APPLE_TEAM_ID!,
signerCert: process.env.APPLE_SIGNER_CERT!,
signerKey: process.env.APPLE_SIGNER_KEY!,
wwdr: process.env.APPLE_WWDR!,
},
google: {
issuerId: process.env.GOOGLE_ISSUER_ID!,
clientEmail: process.env.GOOGLE_CLIENT_EMAIL!,
privateKey: process.env.GOOGLE_PRIVATE_KEY!,
},
});
Then import wallet wherever passes are created:
import { wallet } from "@/lib/wallet";
import { field } from "passlet";
Only need one platform? Omit the other block from wallet.ts entirely — Passlet skips that platform automatically.
Pass Types
Each method maps automatically to the correct Apple pass style and Google Wallet class:
wallet.loyalty(config)
wallet.event(config)
wallet.flight(config)
wallet.coupon(config)
wallet.giftCard(config)
wallet.generic(config)
Creating a Pass
const pass = wallet.event({
id: "summer-fest",
name: "Summer Fest",
fields: [
field.primary("event", "Event", "Summer Fest"),
field.secondary("date", "Date", "Aug 29, 2026"),
field.secondary("location", "Location", "Monterrey, MX"),
field.auxiliary("door", "Doors Open", "6:00 PM"),
field.auxiliary("seat", "Seat", "GA"),
],
barcode: {
format: "QR",
value: "TICKET-2444",
altText: "TICKET-2444",
},
});
const { apple, google } = await pass.create({ serialNumber: "user-123" });
apple → Uint8Array — the .pkpass file. Serve with Content-Type: application/vnd.apple.pkpass.
google → JWT string. Build the save URL as https://pay.google.com/gp/v/save/{jwt}.
Fields
Fields define what shows on the pass. Use the field helper — Passlet translates to each platform's layout model.
import { field } from "passlet";
field.primary("key", "Label", "Value")
field.secondary("key", "Label", "Value")
field.auxiliary("key", "Label", "Value")
field.back("key", "Label", "Value")
Field placement quick reference:
primary → pass title / main value (points balance, event name, passenger name)
secondary → key details (date, tier, gate, seat)
auxiliary → supporting info (doors open, boarding time, promo code)
back → fine print, terms, support contact
Barcodes
barcode: {
format: "QR",
value: "ABC-12345",
altText: "ABC-12345",
}
Passlet normalizes formats across platforms. Unsupported formats fall back to the closest platform equivalent automatically.
Visual Customization
wallet.loyalty({
id: "my-card",
name: "My Card",
backgroundColor: "#1c1917",
foregroundColor: "#fafaf9",
labelColor: "#a8a29e",
icon: readFileSync("./assets/icon.png"),
logo: readFileSync("./assets/logo.png"),
fields: [ ],
});
Colors accept any hex value. Passlet handles Apple asset bundling and Google image hosting references automatically.
Credentials Setup
Apple
Requires an Apple Developer account with a Pass Type ID.
- Create a Pass Type ID
- Create and download the signing certificate from the Apple Developer portal
- Export as
.p12, then convert to PEM:
openssl pkcs12 -in certificate.p12 -clcerts -nokeys -out signerCert.pem
openssl pkcs12 -in certificate.p12 -nocerts -out signerKey.pem
- Download the Apple WWDR G4 certificate
Store all four values (passTypeIdentifier, teamId, signerCert, signerKey, wwdr) as environment variables — never hardcode.
Google
Requires a Google Wallet issuer account.
- Sign up for the Google Pay & Wallet Console
- Create a service account with the Google Wallet API enabled
- Download the JSON key — use
client_email and private_key from the file
Serving Passes
app.get("/pass/:id", async (req, res) => {
const { apple, google } = await pass.create({ serialNumber: req.params.id });
if (req.headers["user-agent"]?.includes("iPhone")) {
res.setHeader("Content-Type", "application/vnd.apple.pkpass");
res.send(Buffer.from(apple));
} else {
const saveUrl = `https://pay.google.com/gp/v/save/${google}`;
res.redirect(saveUrl);
}
});
Pass Type × Field Recommendations
| Pass Type | Primary | Secondary | Auxiliary |
|---|
loyalty | Points balance | Tier, member name | Expiry, next reward |
event | Event name | Date, venue | Doors open, seat |
flight | Passenger name | Gate, seat, flight no. | Boarding time, class |
coupon | Discount value | Offer description | Expiry, promo code |
giftCard | Balance | Card number | Expiry |
generic | Holder / ID name | Key identifier | Supporting detail |
Anti-Patterns — AVOID
- Instantiating
new Wallet() inside a request handler or component — always use the wallet.ts singleton
- Hardcoding credentials in source — always use environment variables
- Omitting
serialNumber on .create() — each issued pass should have a unique serial
- Using
field.primary() for more than 1–2 values — primary fields have very limited display space
- Assuming barcode formats are identical across platforms — let Passlet handle normalization
- Serving the raw
google JWT directly — always wrap it in https://pay.google.com/gp/v/save/{jwt}
Before implementing, confirm with the user:
- Which pass type best fits their use case (
loyalty, event, flight, coupon, giftCard, generic)
- Whether they need Apple, Google, or both (affects which credentials are required)
- Whether passes need to be updated after issuance (pass updates are on the roadmap, not yet available)