Skip to main content
Articles

Authentication + Billing: How Clerk and Stripe Work Together

Author: Roy Anger
Published: (last updated )

How do Clerk and Stripe work together for authentication and billing?

This is the first part of a two-part series on Clerk Billing. Part 1 covers the conceptual architecture, what you can build, and a practical walkthrough for setting up B2C billing. Part 2 covers B2B billing, advanced webhooks, and a build-vs-buy comparison.

Clerk Billing is a native, first-party integration with Stripe. You define your plans and features in the Clerk Dashboard, and Clerk puts each user's (or organization's) plan and feature entitlements directly on the session token you already verify for authentication. You gate paid features with the same has() check and <Show> component you use for auth, add a paywall with the prebuilt <PricingTable />, and let Stripe process the actual payments. It works for individual users (B2C) and for organizations and their members (B2B), and it costs the same as wiring up Stripe Billing yourself: 0.7% per transaction on top of Stripe's fees.

The short version: a paid-feature check needs no extra database lookup, no webhook plumbing, and no subscription-state table to keep in sync.

This first part covers the architecture at a high level, what you can build with Clerk Billing, and a B2C walkthrough. Part 2 continues with the B2B walkthrough, advanced webhooks, the build-vs-buy math, how Clerk compares to other auth providers, and the honest limitations.

Why authentication and billing belong together

Every paid feature in a SaaS app comes down to two questions about the current request. Who is this user? And what have they paid for?

Authentication answers the first question. Billing answers the second. They're usually treated as separate systems, which is how you end up with the classic stack: an auth provider for identity, Stripe for payments, your own database to remember who is subscribed to what, and a pile of glue code holding the three together.

That glue is where the work hides. You have to mirror Stripe's subscription state into your database, keep it current through webhooks, make those webhook handlers idempotent, handle failed payments and retries, and then scatter "is this user allowed?" checks across your codebase, each one reaching for a slightly different source of truth.

The premise behind Clerk Billing is that if identity already rides on the session, the answer to "what have they paid for" should ride there too. Then both questions get answered the same way, in the same place, with the same SDK.

How Clerk Billing works

This section stays conceptual. The goal is to understand the relationship between your app, Clerk, and Stripe before you write any code.

Clerk sits between your app and Stripe

You talk to one SDK. Clerk talks to Stripe.

Stripe's role is narrow and important: it processes payments. In Clerk's words, "Clerk Billing only uses Stripe for payment processing." Card data and the actual money movement stay with Stripe, the PCI-compliant processor, and Clerk is not a merchant of record.

One distinction trips people up, so it's worth stating plainly. Clerk Billing is a separate product from Stripe Billing. You define your plans and prices in the Clerk Dashboard, not in Stripe, and "Plans and Subscriptions made in Clerk are not synced to Stripe." You don't set up Stripe Billing products yourself; you set up Clerk plans, and Clerk uses your Stripe account to charge for them.

Where your plans and features live

Plans and features are configured in the Clerk Dashboard, not in code and not in Stripe.

A plan is what a customer subscribes to (for example, Free, Pro, Enterprise). A feature is a capability you attach to one or more plans (for example, premium_access or reports). Authorization then keys off features and plans rather than hard-coded user lists.

Entitlements ride on the session token

This is the part that changes how the code looks.

When Billing is enabled, a user's active plan and enabled features are encoded as default claims on the version 2 Clerk session token (a JWT). The pla claim holds the active plan in the form scope:planslug, where the scope is u for a user-level plan or o for an organization-level plan (for example, u:free or o:pro). The fea claim holds the enabled features as a scoped list (for example, o:dashboard,o:impersonation).

These are default claims that Clerk manages. You don't add them to a custom JWT template (in fact, you can't).

Because those claims live on the token your app already verifies on every request, and verifying a Clerk session token doesn't require talking to a database, a feature check happens in-process. As Clerk's "How Clerk works" overview puts it, "verifying the JWT doesn't require interacting with a database, the latency overhead and scaling challenges caused by database lookups are eliminated." So has({ plan: 'pro' }) reads the verified token and returns an answer, with no round-trip to Clerk or to your own database.

How subscription state stays in sync

When a customer subscribes, upgrades, downgrades, or stops paying, Clerk updates that customer's plan and feature claims so your next request sees the new state.

Note the direction here, because it's the mirror image of the "not synced to Stripe" point above. Clerk keeps your app's view of the subscription current with what happened in Stripe; it does not push the plans you defined in Clerk into Stripe Billing. Payment events flow from Stripe into Clerk's entitlements; plan definitions do not flow from Clerk into Stripe.

Development gateway vs. your own Stripe account

You can build and test billing before you ever touch a Stripe account.

Clerk's development instances automatically use a Stripe test account, so you can use Stripe test cards to simulate subscriptions, failures, and upgrades with zero configuration. For production, you connect your own Stripe account during setup, and Clerk handles syncing users, payment methods, and transactions. You can link an existing Stripe account as long as it isn't controlled by another platform (an account created under another product's Stripe Connect setup, for example). A development-instance Stripe account is a sandbox and can't be promoted to production.

For testing, use Stripe's standard test cards (for example, 4242 4242 4242 4242 for a successful charge). Stripe's testing documentation lists the full set, including cards that trigger declines and other failure scenarios. Don't use a real card in test mode.

A quick conceptual map

ConceptHow Clerk handles it
Where plans and features are definedThe Clerk Dashboard (Billing and Features), not Stripe
Where a user's plan and features live at request timeOn the session token, as the pla and fea claims
What a feature check costsA local has() read of the verified token, with no database call
Who processes payments and holds card dataStripe
How your app learns about payment changesClerk reflects Stripe's subscription state onto the user; optional webhooks for side effects

What you can build with Clerk Billing

A tour of the surface area before the walkthroughs.

Plans and subscriptions. Monthly or annual pricing, with per-plan free trials. A plan's monthly fee can be null, which lets you offer annual-only plans.

Feature gating and entitlements. Check access on the server with has(), hide or show UI with <Show>, and enforce access at the route level in proxy.ts. These are the same primitives you already use for role and permission checks.

Prebuilt, customer-facing UI. The stable <PricingTable /> renders your plans, handles checkout, and manages upgrades and downgrades. A set of experimental components covers more flows: <CheckoutButton>, <PlanDetailsButton>, and <SubscriptionDetailsButton>, plus billing hooks like useSubscription and useCheckout for fully custom UI.

Per-seat billing (usage-based for B2B). For organizations, a plan can charge per seat: a base fee, a per-seat fee, a number of included seats, and an optional seat limit. Clerk adjusts the seat count to the organization's membership at the start of each billing period, and prorates seats added mid-period. Event or consumption metering (charging per API call or per unit) isn't available yet; it's on the roadmap.

Free trials. Enable a free trial on a single plan or across all plans (minimum 1 day). By default a payment method is required to start a trial, trials are once per customer, and Clerk sends a subscriptionItem.freeTrialEnding webhook (plus an automatic email to the user) 3 days before the trial ends.

B2C and B2B. Bill individual users, bill organizations, or do both in one app. This part walks through B2C below; Part 2 covers the B2B walkthrough.

Setting up Clerk Billing: a practical walkthrough

The order here follows the fastest path to a working paywall: configure plans, drop in a component, then add server-side gating. All code is TypeScript on Next.js 16 (App Router), and all of it uses stable APIs.

Prerequisites

Checklist

Step 1: Define plans and features in the Dashboard

Billing is configured in the Clerk Dashboard, so this step has no code.

  1. Enable Billing under Billing → Settings. In a development instance this connects the shared Stripe test account automatically; for production you connect your own Stripe account here.
  2. Create your plans under Billing → Subscription plans. The User tab holds plans for individual users (B2C); the Organization tab holds plans for organizations (B2B).
  3. Create features under Features and attach them to plans. Mark a plan publicly available when you want it to appear in the pricing table.

Step 2: Add a pricing table

Render your plans with the stable <PricingTable /> component on a dedicated route. Imported from @clerk/nextjs, it shows the plans you marked public and handles the whole checkout flow.

import { PricingTable } from '@clerk/nextjs'

export default function PricingPage() {
  return <PricingTable />
}

That single component is your paywall's storefront. By default it renders plans for the individual user; switching it to organization mode for B2B is covered in Part 2.

The fastest path: the hosted Account Portal

If you want to ship before building any routes, the Account Portal is the shortcut.

The Account Portal is a set of pages Clerk hosts for you on a Clerk-managed domain: sign-in, sign-up, and user and organization profile management. Once Billing is enabled, the hosted profile pages include subscription management, so your customers can subscribe and manage their plans with no billing UI for you to build.

There's a styling tradeoff worth knowing up front. Account Portal pages are customizable through Dashboard theming, but the appearance prop does not apply to them. When you want the appearance prop (theme, variables, and per-element CSS) or you want to control where billing UI sits inside your own app, you embed the components in your own routes instead. The hosted flow's internal layout is fixed either way; embedding mainly buys you the appearance prop and placement control, not the ability to rewrite the component's markup.

So treat the Account Portal as the fast on-ramp, not the only option. The rest of this walkthrough embeds components directly, which is the natural progression once you want more control.

B2C example: gating by an individual user's plan

Three things to wire up: a server check, a UI gate, and a way to send non-subscribers to your pricing page.

Check a plan or feature on the server with has()

In a Server Component, pull has off await auth() and check the plan or feature. This is the authoritative check, because it runs on the server against the verified token.

import { auth } from '@clerk/nextjs/server'

export default async function ProDashboardPage() {
  const { has } = await auth()

  if (!has({ plan: 'pro' })) {
    return <h1>Only subscribers to the Pro plan can access this content.</h1>
  }

  return <h1>Welcome to the Pro dashboard</h1>
}

To gate on a specific capability instead of a whole plan, check a feature with has({ feature: 'premium_access' }). Feature checks tend to age better than plan checks, because you can move a feature between plans in the Dashboard without touching code.

Gate UI on the client with <Show>

For showing and hiding parts of a page, the <Show> component takes a when condition and an optional fallback.

import { Show } from '@clerk/nextjs'

export default function DashboardPage() {
  return (
    <Show when={{ plan: 'pro' }} fallback={<p>Upgrade to Pro to access the dashboard.</p>}>
      <h1>Pro dashboard</h1>
    </Show>
  )
}

The when prop also accepts 'signed-in', 'signed-out', { feature: '...' }, { role: '...' }, { permission: '...' }, or a callback (has) => boolean.

Warning

<Show> only hides its children visually. The content is still in the page source, so it isn't a security boundary. Always pair it with a server-side has() check (or a route-level gate) for anything that actually needs protecting.

Send non-subscribers to upgrade

You have a few options, in increasing order of how hard they enforce the gate.

The lightest is the fallback you already saw: render an upgrade message in place of the gated content. The next step up is to redirect from the Server Component:

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

export default async function ProDashboardPage() {
  const { has } = await auth()

  if (!has({ plan: 'pro' })) {
    redirect('/pricing')
  }

  return <h1>Welcome to the Pro dashboard</h1>
}

The strongest option enforces the gate at the route level in proxy.ts (the Next.js 16 file that replaces middleware.ts). Match the protected routes, then call auth.protect() with a plan check. It redirects an unauthenticated visitor to sign-in, and the unauthorizedUrl option sends a signed-in user who lacks the plan to your pricing page.

import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'

const isPremiumRoute = createRouteMatcher(['/premium(.*)'])

export default clerkMiddleware(async (auth, req) => {
  if (isPremiumRoute(req)) {
    // Unauthenticated visitors go to sign-in; signed-in users without `pro` go to /pricing
    await auth.protect((has) => has({ plan: 'pro' }), { unauthorizedUrl: '/pricing' })
  }
})

export const config = {
  matcher: [
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    '/(api|trpc)(.*)',
    '/__clerk/(.*)',
  ],
}

A note on the gating API: the callback form shown here — auth.protect((has) => has({ plan: 'pro' })) — reuses the exact has() logic you use in a Server Component, so a plan or feature gate reads the same at the route level as it does inside a component. (Clerk also accepts a direct params object, auth.protect({ plan: 'pro' }, { unauthorizedUrl: '/pricing' }); the callback form is shown because it composes more involved checks cleanly.) By default a signed-in user who fails the check gets a 404; the unauthorizedUrl option turns that into a redirect, while an unauthenticated visitor is sent to sign-in automatically. A plain server-side has() check (as in the earlier examples) is the alternative when you'd rather render your own fallback than redirect. On Next.js 16, proxy.ts only changes the filename: the Clerk imports and code are identical to middleware.ts, and the callback doesn't need to return NextResponse.next() unless you're redirecting or rewriting.

Frequently asked questions

Do I need to create products in Stripe? No. You define your plans and features in the Clerk Dashboard, and Clerk handles creating the necessary products and prices in your connected Stripe account.

Can I test billing without a real Stripe account? Yes. Clerk's development instances automatically use a Stripe test account, allowing you to build and test your billing flow with Stripe test cards before connecting your own production Stripe account.

Does a feature check require a database lookup? No. A user's active plan and features are encoded directly on the Clerk session token. When you call has(), it reads the verified token in-process without making a database or network call.

Continue to Part 2 for the B2B walkthrough, advanced webhooks, and a build-vs-buy comparison.

In this series

  1. Authentication + Billing: How Clerk and Stripe Work Together (you are here)
  2. Authentication + Billing: How Clerk and Stripe Work Together - Part 2