Quick Start
Build your first subscription flow in 5 minutes.
This guide assumes you've completed Installation and have a working birrjs instance with a mounted route handler.
1. Define a plan
import { feature, plan } from "@birrjs/core";
const messages = feature({ id: "messages", type: "metered" });
const analytics = feature({ id: "analytics", type: "boolean" });
export const free = plan({
id: "free",
name: "Free",
default: true,
price: { amount: 0, interval: "monthly" },
includes: [messages({ limit: 100, reset: "month" })],
});
export const pro = plan({
id: "pro",
name: "Pro",
price: { amount: 29, interval: "monthly" },
includes: [messages({ limit: 5000, reset: "month" }), analytics()],
});2. Add plans to your instance
import { createBirr } from "@birrjs/core";
import { chapa } from "@birrjs/chapa";
import { free, pro } from "./plans";
export const birrjs = createBirr({
database: process.env.DATABASE_URL!,
provider: chapa({
secretKey: process.env.CHAPA_SECRET_KEY!,
webhookSecret: process.env.CHAPA_WEBHOOK_SECRET!,
callbackUrl: process.env.CALLBACK_URL!,
}),
plans: [free, pro],
identify: async (request) => {
const session = await auth.api.getSession({ headers: request.headers });
if (!session) return null;
return { customerId: session.user.id, email: session.user.email };
},
});3. Sync plans to the database
birrjs pushThis runs pending migrations and syncs your code-first plans into the database.
4. How customers work
Customers are created automatically when identify returns a user ID on the first client request. The customerId you return becomes the BirrJS customer record key — no separate creation step needed.
identify: async (request) => {
const session = await auth.api.getSession({ headers: request.headers });
if (!session) return null;
return { customerId: session.user.id, email: session.user.email };
},BirrJS doesn't handle auth — identify receives the raw Request and returns your app's user ID. Wire in your own session resolver (BetterAuth, NextAuth, Clerk etc.).
For server-side code (admin dashboards, cron jobs) where there is no HTTP Request object, use createCustomer directly:
const customer = await birrjs.createCustomer({
email: "user@example.com",
name: "Jane Doe",
});
// customer.id is auto-generated5. Subscribe to a plan
The identify option links each request to a customer automatically. On the client, you don't need to pass a customerId — the SDK resolves it from the session.
First create the client:
import { createBirrJSClient } from "@birrjs/core/client";
import type { birrjs } from "./birrjs";
export const client = createBirrJSClient<typeof birrjs>();Subscribe can be called from the client or the server. Both return a checkoutUrl to redirect the user to the payment gateway.
import { client } from "./birrjs-client";
async function handleSubscribe() {
const { checkoutUrl } = await client.subscribe({ planId: "pro" });
// redirect to Chapa checkout
window.location.href = checkoutUrl;
}import { birrjs } from "../lib/birrjs";
export async function POST(req: Request) {
const { customerId } = await req.json();
const { checkoutUrl } = await birrjs.subscribe({
customerId,
planId: "pro",
});
return Response.json({ checkoutUrl });
}For free plans, the subscription activates immediately with no redirect.
6. Check usage
Use check to verify a customer has remaining balance, then report to decrement. Call these server-side with the customer ID.
import { birrjs } from "./birrjs";
async function sendMessage(customerId: string) {
const { allowed } = await birrjs.check({
customerId,
featureId: "messages",
required: 1,
});
if (!allowed) {
throw new Error("Monthly message limit reached");
}
// process the message...
await birrjs.report({
customerId,
featureId: "messages",
amount: 1,
});
}For boolean features like analytics, check returns allowed without any balance tracking.
Need to react to billing events? Use the on option on createBirr() for simple handlers (email, SMS), or build a plugin for advanced use cases.