# passkit-generator separates the pass model from the pass you issue

> This library generates Apple Wallet passes in Node.js and its central design decision is the split between a model, the static assets and identity that never change, and an application, the per-user data patched in at runtime. That split supports reusing static assets across personalised passes and dictates where your code goes.

**alexandercerutti/passkit-generator** — The easiest way to generate custom Apple Wallet passes in Node.js

- Repository: https://github.com/alexandercerutti/passkit-generator
- Stars: 1,265 · Forks: 144
- Language: TypeScript
- License: MIT
- Published: 2026-10-17 · Updated: 2026-10-17 · Language: en
- Canonical page: https://hysenlabs.com/projects/alexandercerutti-passkit-generator

## The model and application split

The architecture section is the part of this README worth reading twice, because it decides how you structure the code around it.

The library was built around a deliberate separation between the application and the model, described as a preprocessed entity. The intent is to split as much as possible the static objects, such as logo, background and icon, from the dynamic ones, such as translations, barcodes and serial numbers, while keeping in mind the different possible execution contexts.

The reason this matters is stated next: pass creation and population might not fully happen in runtime. A server that issues different passes can reuse the same static assets rather than adding them to every pass.

So the model holds the pass identity: the icon, the thumbnails, the pass type identifier, the team identifier, the colours, and anything else you know will not be customised per request. The application supplies what changes.

The mechanics are simple once you have that split. Files added through a scratch model, a folder template or buffers are read and pushed into the resulting.zip as they are. Dynamic data is patched into `pass.json`, and `manifest.json`, the `signature` and the translation files are generated at runtime.

## Three ways to start, and why the folder needs .pass

There are three sources for a model, and the README ranks them by convenience rather than by power.

Creating from scratch is for advanced use. A buffer model is for when you already have the files in memory, for example a `model.zip` you unzipped at startup. The folder model, a template directory on disk, is the suggested starting point when you have no model yet, because it gives you direct access to the files and data.

The README gives the cloud function case as the reason the other two exist: a serverless function might prefer a scratch model for faster startup, rather than storing a model in a data bucket and fetching it on every cold start.

One rule will stop you if you miss it. Using the `.pass` extension for the directory is a best practice that shows the folder is a pass package, and the package goes further and requires every folder model to carry that extension. If you omit it in configuration it is forcefully added, which the README notes can produce a folder reading error if your model directory does not actually have it.

To keep a model resident across a run, you create a `PKPass` instance once and then reuse it as a template through `PKPass.from`.

## The two details that break passes

One is the icon. Apple's documentation says the icon is required for a pass to work, and this README adds the sharp version: omitting an icon resolution might make a pass work on one device, a Mac for example, but not on another, an iPhone. That is a genuinely nasty failure mode, because it appears at QA rather than at build.

The other is which files the library owns. `manifest.json` and `signature` are automatically ignored from the model and generated at runtime, so putting them in your model does nothing and can confuse you when a stale signature appears to be used.

Localisation is the third. You create `.lproj` folders such as `en.lproj` or `it.lproj` containing localized media, and to include one of those files through a buffer model you name the key with the path, for example `en.lproj/thumbnail.png`.

A pass cannot be signed without certificates, and the README's advice is conventional: keep a dedicated folder such as `./certs` for them. It uses OpenSSL for that work and tells you to have it installed, noting you would do the same thing without this library. You need both the developer certificate and the WWDR certificate.

The pass.json itself is small and mostly identity:

## pass.json, installation and the build

The starting pass.json in the README is deliberately minimal, with placeholders for the values you supply:

```json
{
	"formatVersion": 1,
	"passTypeIdentifier": "pass.<bundle id>",
	"teamIdentifier": "<your team identifier>",
	"organizationName": "<your organization name>",
	"description": "A localizable description of your pass. To do so, put here a placeholder.",
	"boardingPass": {}
}
```

The pattern to notice is that the description holds a placeholder rather than final copy, because the real text belongs in the localisation files and is patched per language.

Installation is a single command:

```sh
npm install passkit-generator --save
```

The package is dual-published, which matters if you have both module systems in a codebase. `package.json` sets `type` to `module` while the exports map offers a `require` path to `lib/cjs/index.js` and an `import` path to `lib/esm/index.js`, with types resolved separately. The build produces both from two tsconfig files using tsconfig-to-dual-package.

Runtime dependencies are only four: `do-not-zip` for archive handling, `joi` pinned at 17.13.4 for validation, `node-forge` for the certificate and signature work, and `tslib`. That `joi` pin is worth noticing, since a validation library pinned to an exact version in a library's dependencies is a choice that can conflict in a consuming application.

The `engines` field says `>=14.21.3`, with a `.nvmrc` at the root for a specific version, and tests run under Jest with the experimental VM modules flag enabled.

## Versioning, funding and what this library does not do

The current version is 3.7.0, tagged on 2026-10-14, after v3.6.1 in September 2026 and v3.6.0 earlier that month. Recent releases have been frequent, and there is a migration guide from v2 to v3. The v2 tag at v2.0.8 is kept on the repository for reference only.

The v2 to v3 break is worth understanding before you start, since migrating after building a model and a set of templates is not cheap. The README points to the migration guide rather than summarising it, so if you are on v2 the wiki is the place to look before upgrading.

The scope boundary is clear and is drawn by what the author links to instead of what he built here. Apple Wallet web services are addressed by separate projects linked in the README: Passkit Webservice Toolkit and `hapns`, which supports updates through APNs notifications with examples.

So passkit-generator produces the.pkpass bundle and stops there. Registering passes, serial numbers on the server, and push updates are a separate build.

There is also a companion tool, Passkit Visual Designer at pkvd.app, for designing a model through a user interface and exporting a.zip you decompress and use as the source. That solves the initial assembly problem for people who would otherwise hand-build the directory.

Examples in the repository cover Cloudflare Workers, Firebase, self-hosted and serverless deployments, which is a decent spread of the places this library tends to end up running.

On licensing it is MIT, the project accepts financial contributors through Open Collective, and the last push was on 2026-10-14.

## Conclusion

Adopt passkit-generator if you are issuing Apple Wallet passes from a Node.js service and can define a pass type up front, since the model and application split pays off across many personalised passes. Do not adopt it if you need the Apple Wallet web services to update already-issued passes, because that is a separate project, the author's passkit-webservice-toolkit with hapns for APNs pushes. Verify first that your pass model is a folder ending in .pass containing an icon, that you hold the developer and WWDR certificates OpenSSL needs, and that your Node version satisfies the engines field.

## FAQ

### How do I install passkit-generator?

With `npm install passkit-generator --save`. The package ships as dual CommonJS and ES module builds, with separate type definitions, and declares support for Node 14.21.3 or newer.

### What is a pass model in passkit-generator?

It is the static half of a pass: the icon, thumbnails, pass type identifier, team identifier and colours, plus the folder structure. It is created from scratch, from a folder template, or from buffers, and folder models must carry the .pass extension.

### Which files does passkit-generator generate at runtime?

manifest.json, the signature and the translation files are generated at runtime and are automatically ignored if they appear in your model. pass.json is patched with the dynamic values you supply.

### Can passkit-generator update passes that are already on a user's device?

No. It generates the pass bundle. Apple Wallet web services support is in separate projects by the same author, a Passkit Webservice Toolkit and hapns for pushing updates through APNs notifications.

## Sources

- [alexandercerutti/passkit-generator on GitHub](https://github.com/alexandercerutti/passkit-generator)
- [Issues](https://github.com/alexandercerutti/passkit-generator/issues)
- [License: MIT](https://github.com/alexandercerutti/passkit-generator/blob/master/LICENSE)
- [README](https://github.com/alexandercerutti/passkit-generator/blob/master/README.md)
- [Releases](https://github.com/alexandercerutti/passkit-generator/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/alexandercerutti-passkit-generator
