All skills
clerk avatar

/clerk-billing

@47c55cd official
by clerkclerk/skills83 stars
5

Clerk Billing for subscription management - render Clerk's PricingTable and in-app checkout drawer, configure subscription plans, seat-limit plans for B2B, feature entitlements with has(), and billing webhooks. Use for SaaS monetization, plan gating, checkout flows, trials, invoicing, and subscription lifecycle management.

Use this Skill: https://skilld.dev/gh/clerk/skills/clerk-billing

This session only. Nothing lands on disk.

referencesb2b-patterns.md

≈1.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

B2B Billing Patterns

Overview

B2B billing in Clerk attaches subscriptions to organizations, not individual users. Each org gets its own subscription. Plans can carry a seat limit (membership cap) which Clerk enforces on member invites.

Create the plan as an Organization Plan, not a User Plan. Use Dashboard → Billing → Plans (Organization Plans tab) or clerk config patch with billing.plans. Slugs are scoped per type. A team plan registered under User Plans will not appear in <PricingTable for="organization" />, and vice versa. Plan type cannot be changed after creation, recreate if misplaced.

Core Pattern: Org-Level Plan Check

import { auth } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'

export default async function TeamDashboard() {
	const { orgId, has } = await auth()

	if (!orgId) {
		redirect('/sign-in')
	}

	if (!has({ plan: 'org:team' })) {
		redirect('/billing')
	}

	return <TeamFeatures />
}

Always check orgId first. If the user has no active org, has({ plan }) evaluates against the user's personal subscription (which may not exist).

Seat-Limit Plans

Clerk Billing's B2B model is seat-limit plans: each organization plan has a fixed price and an optional membership cap; Clerk enforces the cap at invite/join time. To charge larger orgs more, create tiered plans (e.g. starter capped at 5, team at 10, enterprise unlimited) with increasing fixed prices.

Key invariants:

  • Fixed price per plan, not auto-scaling per member. Adding members does not increment the org's billing amount on the active plan.
  • One active SubscriptionItem per payer per Plan. Do not derive seat count from items.length.
  • Seat limit is a Plan property. Set it when creating the plan (Dashboard → Billing → Plans → Organization Plans tab, or clerk config patch); it cannot be changed later.
  • When an org exceeds or changes to a plan with a lower limit, existing members stay but new invites are blocked until the org is under cap. See Plans with seat limits for the exact admin behavior.

No custom seat-counting code is needed. Read the active plan with has({ plan: 'org:team' }) and let Clerk enforce membership limits.

Org Billing Page

Use <OrganizationProfile /> for the org account billing UI. It renders the active org plan, members, invitations, and the upgrade / cancellation flow scoped to the active organization, with admin-only access to billing actions enforced by Clerk:

import { OrganizationProfile } from '@clerk/nextjs'

export default function OrgAccountPage() {
	return <OrganizationProfile />
}

Organization Plans configured in Dashboard → Billing → Plans automatically appear inside <OrganizationProfile /> (in the Plans section). Only org admins see the billing controls. Build a custom page only when you need branded layouts or to embed <PricingTable for="organization" /> outside the OrganizationProfile shell.

Webhook: Org Subscription Events

if (evt.type === 'subscription.created') {
	const { id, payer, items, status } = evt.data
	if (payer.organization_id) {
		const plan = items[0]?.plan?.slug
		await db.orgSubscriptions.upsert({
			where: { orgId: payer.organization_id },
			create: {
				orgId: payer.organization_id,
				plan,
				subscriptionId: id,
				status,
			},
			update: { plan, subscriptionId: id, status },
		})
	}
}

if (evt.type === 'subscription.updated') {
	const { id, payer, items, status } = evt.data
	if (payer.organization_id) {
		const plan = items[0]?.plan?.slug
		await db.orgSubscriptions.update({
			where: { orgId: payer.organization_id },
			data: { plan, status },
		})
	}
}

Use payer.organization_id (nested under payer, not a top-level org_id) when the subscription belongs to an organization. Do NOT use items.length as a seat count, seat limits are set at the plan level and there is only one active SubscriptionItem per payer per Plan.

Plan Naming for B2B

Tier plans by seat cap so bigger orgs pay more:

Plan Slug Seat cap
Startup org:starter 5
Team org:team 10
Business org:business 25
Enterprise org:enterprise unlimited (requires B2B Authentication add-on)

Define these via Dashboard → Billing → Plans → Organization Plans tab with Seat-based toggled on, or via clerk config patch with billing.plans. Use the org: prefix in slugs to disambiguate org plans from user plans in code (has({ plan: 'org:team' }) vs has({ plan: 'team' })). Seat caps above 20 and "unlimited" require the B2B Authentication add-on.

Common Mistake: Checking Plan Without Active Org

// WRONG, user has no active org, has() checks user subscription
const { has } = await auth()
if (!has({ plan: 'org:team' })) redirect('/billing')

// CORRECT, check orgId first
const { orgId, has } = await auth()
if (!orgId) redirect('/sign-in')
if (!has({ plan: 'org:team' })) redirect('/billing')

Source: SKILL.md on GitHub

1 warning6d3 checks · Risk SAFE
  • Gen Agent Trust Hub6d

    The skill provides comprehensive instructions for integrating Clerk Billing into Next.js applications, including subscription management, UI components, and webhook handling. It utilizes official Clerk CLI tools and SDKs and emphasizes security best practices such as webhook signature verification.

  • Socket6d

    No alerts

  • Snyk6d

    Risk: MEDIUM · 1 issue

Signed by skilld at 47c55cd. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated last week
What it can do
Network
metadata
{
  "author": "clerk",
  "version": "1.1.0"
}
All 1 allowed tools
WebFetch
Other metadata
compatibility
Requires NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY, and CLERK_WEBHOOK_SIGNING_SECRET. Billing must be enabled in Clerk Dashboard → Billing. Development instances can use the shared Clerk development gateway; production instances require a Stripe account for payment processing.
  • Next.js
  • clerk
  • billing
  • saas
  • subscriptions
  • pricing
  • stripe
  • feature-gating
  • b2b
  • webhooks

README badge

README badge for clerk/skills/clerk-billing

Configures subscription plans, seat-limit tiers for B2B, and feature entitlements in Clerk, then gates access via `has({ plan })` and `has({ feature })` checks. Renders pricing tables and checkout flows with `<PricingTable />`, manages billing webhooks for subscription lifecycle, and supports both user and organization subscriptions via Next.js Server and Client Components.

Generated from the current SKILL.md.

Do I need a Stripe account to use Clerk Billing?
No for development. Dev instances can use Clerk's shared development gateway. Production requires a Stripe account for payment processing only.
What's the difference between has({ plan }) and has({ feature })?
Use has({ feature }) to gate specific capabilities like export or analytics. Use has({ plan }) to gate by subscription tier. Features are assigned to plans and scoped per plan.
Can I configure billing plans programmatically without the Dashboard?
Yes. Use clerk enable billing, clerk config pull, and clerk config patch commands to manage plans and features via CLI, or make raw PATCH requests to the PLAPI.
Does this support B2B organization subscriptions and seat limits?
Yes. Use <PricingTable for="organization" /> to render org-level plans, and configure seat-limit plans to enforce membership caps that Clerk enforces at invite time.
What happens if I render <PricingTable /> before enabling Billing?
In development it throws a cannot_render_billing_disabled error. In production it renders empty. Billing must be enabled in the Dashboard or via clerk enable billing first.

Generated from the current SKILL.md. These answers refresh after source changes.