paykit keeps Stripe billing state in your own database
Build Stripe billing without the glue code. Products in code, webhooks handled, billing state in your app.
At a glance
- What is it?
- paykit is an open-source TypeScript billing framework that runs inside your application, defines products, plans, entitlements and metered features as code, and handles the Stripe subscription lifecycle and webhooks while the billing state stays in your own database. It is published on npm as paykitjs, is MIT licensed, and at version 0.2.0 it is young enough that the readme's simplicity is the main argument for it.
- Who is it for?
- paykit is worth evaluating if you are a TypeScript team that wants to keep its billing data in its own database and has decided Stripe is the provider, because the code-defined plan and feature model is a legible answer to the most tedious part of a subscription product. It is the wrong tool if you want a hosted billing platform that also does the customer portal, the invoices, the tax and the dunning, since this framework is scoped to the entitlement and lifecycle layer.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 26 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The middle ground between raw Stripe and a hosted platform
The problem PayKit describes is a genuine fork in the road for a subscription product. Billing in the TypeScript ecosystem is either handled through low-level provider SDKs that require a lot of glue code, or through hosted platforms that own your billing data and sit between you and your customers. PayKit's answer is a third option, an open-source framework that runs inside your app, keeps billing state in your own database, and handles the Stripe lifecycle so you do not have to. The stated surface is a typed API for products, subscriptions, entitlements and usage billing without stitching together raw Stripe APIs and webhooks. The audience is a team that has already chosen Stripe, that wants its own database to remain the source of truth for who is entitled to what, and that is tired of writing the same subscription state machine in every project. The pricing model, in other words, is not a feature of the tool but the reason it exists.
Features, plans and limits are declared as values
The data model is the interesting part, and it is small. A feature is declared with an id and a type, and a metered feature is one you can set a limit on:
import { createPayKit, feature, plan } from "paykitjs";
const messages = feature({ id: "messages", type: "metered" });A plan then references features with a limit and a reset period, and carries a price:
const free = plan({
id: "free",
group: "base",
default: true,
includes: [messages({ limit: 100, reset: "month" })],
});
const pro = plan({
id: "pro",
group: "base",
price: { amount: 19, interval: "month" },
includes: [messages({ limit: 2_000, reset: "month" })],
});The design decisions here are worth naming. The free plan is marked default, so there is a defined answer to what a user gets before they subscribe, and the group field means plans are organised into tiers rather than being a flat list. The limit and reset pair on a feature is what makes entitlement checking cheap in your application, because the question you ask your database is whether the current usage exceeds the limit for the plan the customer is on, rather than a set of conditionals in your feature flag code. A price is an amount paired with an interval, so a plan stays a small declarative value.
One call wires the Stripe keys, the database and the products
The entry point is createPayKit, and it takes three things, the Stripe credentials, the database connection and the products you declared:
export const paykit = createPayKit({
stripe: {
secretKey: process.env.STRIPE_SECRET_KEY!,
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
},
database: process.env.DATABASE_URL!,
products: [free, pro],
});Two details are load-bearing. The webhook secret is a separate value from the API key, and it is the one that lets the framework verify that an incoming webhook really came from Stripe rather than from someone posting to your endpoint, so its presence in the configuration is the framework telling you the security boundary is there. And the database is a URL, not a connection object, which means the framework owns the connection and can create its own schema, which is consistent with the promise that billing state lives in your database. The claim in the README is that this single setup gives you products, subscriptions, entitlements and usage billing, with the Stripe lifecycle and the webhooks handled, so the code you write on top is entitlement checks rather than event handling.
Getting started is one command, and the docs sit off the repo
The installation path is a single initialiser:
npx paykitjs initand the README also links an installation guide in the documentation site, along with the main docs at paykit.sh/docs, a Discord, and a Twitter account. The package published to npm is paykitjs, and the badges in the README point at that package for its version and its download count, so the name on npm and the name of the project differ, which is worth knowing when you search for it later. The repository layout is a pnpm workspace with turbo, a packages/ directory for the published code, an apps/ directory holding the documentation site and a demo, an e2e/ directory, a dev/ directory, and scripts/ for the release tooling. The root package.json is private and named paykitjs-root, requires Node 22 or newer, and pins pnpm 12.4.2, so the tooling expectations are current even though the package version is not.
End-to-end tests want a real database and real Stripe keys
The repository is honest about what its tests need, and the .env.example is a good guide to how the project is meant to be exercised. The end-to-end block asks for a PostgreSQL database, a Cloudflare tunnel token, and a pair of Stripe credentials named E2E_STRIPE_SK and E2E_STRIPE_WHSEC, which correspond to a test secret key and a test webhook secret. The demo block asks for an app URL, an auth database, a PayKit database, the Stripe secret key and webhook secret, an auth secret, and a key for a service named Autumn. The presence of a separate database URL for PayKit alongside the application's own suggests that the framework's schema can live in its own database rather than in the main one, which is a choice worth making deliberately, since it affects how you join entitlement data to your own tables. The tooling around it is conventional for a modern TypeScript monorepo, with oxlint for linting, oxfmt for formatting, vitest for unit tests through vitest.unit.config.ts, lefthook for git hooks, and changesets for versioning.
What PayKit deliberately does not do
The boundary matters more than the feature list. PayKit is a billing framework for products, subscriptions, entitlements and usage, and it is not a customer-facing billing portal, an invoicing system, a tax engine, a dunning service or a subscription management UI. Those are the things a hosted platform gives you when you accept that it owns your billing data, and they are the things you keep if you choose the raw provider SDKs. The trade PayKit is offering is that you get the entitlement layer, the Stripe lifecycle handling and the webhook plumbing for free, and you keep the customer-facing surface in your own application, where you can build it the way you want. The price of that choice is the work of building the parts PayKit does not cover. The second boundary is maturity. The releases are v0.0.5 from 2026-05-08, v0.0.5-alpha.0 the same day, and v0.2.0 on 2026-09-17, with the last push on 2026-09-27, so the API has already moved once and the version number is still pre-1.0 with no compatibility promise in the README.
MIT, sponsored, and a security contact
The licence is MIT with a LICENSE file in the repository root, and the project describes itself as a free and open-source project, with contributions invited through the issue tracker. It is maintained with sponsorship, and the README pulls the sponsor list in live from GitHub Sponsors through a shield service, listing companies and individuals who help keep the project maintained. For a component that sits on the payment path, the security contact is the part to note, an address at [email protected], with the promise that reports are addressed promptly and the reporter credited. The repository also carries AGENTS.md, CLAUDE.md, a .codex/ directory and a .cursor/ directory, which tells you the project expects machine agents to work on it, and there is a changesets directory, so the versioning is managed with changesets and the release flow runs a turbo build over the packages followed by a publish script.
Editorial conclusion
paykit is worth evaluating if you are a TypeScript team that wants to keep its billing data in its own database and has decided Stripe is the provider, because the code-defined plan and feature model is a legible answer to the most tedious part of a subscription product. It is the wrong tool if you want a hosted billing platform that also does the customer portal, the invoices, the tax and the dunning, since this framework is scoped to the entitlement and lifecycle layer. Before adopting it, read the changelog between 0.0.5 and 0.2.0 and accept that the API is still moving, check the end-to-end setup in the repository because it needs a PostgreSQL database and real Stripe test credentials, and keep the Stripe secret key and webhook secret in the environment rather than in the code, exactly as the example configuration does.
Frequently asked questions
What does paykit do?
It is an embedded Stripe billing framework for TypeScript apps that runs inside your app, uses your database, and gives you a typed API for products, subscriptions, entitlements and usage billing, handling the Stripe lifecycle and the webhooks for you.
How are plans and metered features defined in paykit?
A feature is declared with an id and a type, a metered one being feature({ id: "messages", type: "metered" }), and a plan includes features with a limit and a reset period, carries a price such as { amount: 19, interval: "month" }, a group, and can be marked as the default plan.
How do I set up paykit in a TypeScript project?
Run npx paykitjs init, or follow the installation guide on paykit.sh. Then call createPayKit with your Stripe secret key and webhook secret, your database URL, and the products you declared, for example createPayKit({ stripe: { secretKey: ..., webhookSecret: ... }, database: ..., products: [free, pro] }).
What does the paykit end-to-end test setup require?
The .env.example asks for a PostgreSQL test database URL, a Cloudflare tunnel token, and Stripe test credentials for the end-to-end suite, plus a separate PayKit database URL and Stripe keys for the demo app. The unit test suite runs on vitest through vitest.unit.config.ts.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/getpaykit-paykit)Was this useful?