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.ts computes 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 (limits in docs/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 + PricingTable for plan selection
  • SubscriptionCard for 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).