Customization Overview
Customize billing for your specific business needs
This section covers advanced billing customization for your specific business needs.
Seat-Based (Quantity) Billing
For organization billing, Makerkit can charge based on a seat count (“quantity billing”). The default behavior:
- Seats are computed from the organization member count at checkout time
- Seats have a minimum of 1
- If the member count can’t be determined, checkout fails (to avoid underbilling)
Where it happens:
packages/billing/ui/src/services/billing-actions.service.tscomputes seats for org checkouts.
Provider notes:
- Stripe: seats are passed to Stripe via Better Auth’s Stripe subscription upgrade API.
- Polar: seats are currently not applied (Polar checkout is user-centric).
If your product’s “billable seats” differs from “organization members” (e.g., exclude guests, count only active users, count licensed users, etc.), customize the seat computation in the billing actions service.
Seat enforcement (org invitations)
Stripe’s seats value is purchased quantity, not “seats available”.
If you want to prevent orgs from exceeding their purchased quantity, you must enforce it in your app logic when:
- inviting members
- accepting invitations
See: Seat enforcement for organization invites
If you want to require billing before orgs can invite/add members (even when there is no seat quantity), implement it via the Policy API (see “Requiring a subscription” in Seat enforcement for organization invites).
Feature Gating With Plan Limits
The billing client exposes config-based plan limits to help you gate features:
import { getBilling } from '@kit/billing-api';import { auth } from '@kit/better-auth';const billing = await getBilling(auth);const { allowed, current, limit } = await billing.checkPlanLimit({ referenceId: organization.id, // or user.id for personal billing limitKey: 'seats', currentUsage: memberCount,});if (!allowed) { throw new Error(`Seat limit reached (${current}/${limit})`);}Notes:
- Limits come from your billing config (
limitsindocs/billing/billing-configuration). - Limits are not enforced automatically — you decide where to check them in your app.
Entitlements (Stripe-only in this kit)
If you want boolean “feature flags” tied to subscription, Stripe Entitlements can be used via:
billing.checkEntitlement(customerId, lookupKey)billing.listEntitlements(customerId)
The kit returns safe defaults when entitlements are unsupported.
Customizing Stripe Checkout / Customer Creation
The Stripe integration is configured in packages/billing/stripe/src/stripe-plugin.ts. Common customizations include:
- Checkout session parameters (tax, billing address collection, metadata)
- Org customer creation metadata (e.g. add
organizationId/slug) - Logging and post-create callbacks
- Wiring additional Stripe events (e.g. trial reminders via
customer.subscription.trial_will_end)
See also: Stripe Setup and Lifecycle Hooks.
Customizing the Billing UI
The billing UI is implemented in packages/billing/ui/src/components/ and is designed to be composable:
PlanPicker+PricingTablefor plan selectionSubscriptionCardfor subscription display and actions- Portal / cancel / restore dialogs
For the web app, billing lives at /settings/billing (see apps/web/app/[locale]/(internal)/settings/billing/page.tsx).