Claude Code Boilerplate
FeaturesPricingBlogDocs
Get started →

Product

  • Features
  • Pricing
  • Skills

Compare

  • vs ShipFast
  • vs MakerKit
  • vs supastarter

Resources

  • Docs
  • Blog
  • Discord

Legal

  • License
  • Privacy Policy
  • Terms of Service
Claude Code Boilerplate

© 2026 Claude Code Boilerplate. All rights reserved.

← All posts

Usage-based billing in Next.js -- charge customers for what they use with Stripe Meters and Drizzle ORM

July 2, 2026
nextjsstripedrizzle-ormsaaspayments

Most SaaS products start with flat-rate subscriptions: $29/month, $99/month, take it or leave it. That works fine when every customer uses roughly the same amount. But the moment you add an AI feature, a per-seat model, or anything that scales with usage, flat pricing punishes your best customers and rewards your lightest ones.

Usage-based billing fixes that. Customers pay for what they actually consume. You earn more as they grow. And your pricing aligns with the value you deliver.

Here is how to wire it up in a Next.js SaaS using Stripe Meters and Drizzle ORM -- without touching your existing subscription flow.

Why flat-rate pricing breaks for AI and usage-heavy SaaS

If you charge $29/month and one customer sends 10 AI messages while another sends 10,000, you are subsidizing the power user and overcharging the light one. Flat pricing works at the median. It fails at the edges -- and the edges are where your most valuable customers live.

Metered billing solves this by recording what each customer actually does and billing for it at period end. Stripe Meters handle the math and the invoicing. Your job is to record the events.

What you are building

Adding usage-based billing to a Next.js SaaS involves four moving parts:

  • A database table to buffer usage events before they are reported to Stripe
  • A service call that records an event every time a customer does something billable
  • A background job that flushes the buffer to Stripe Meters on a schedule
  • A usage summary your customers can see before their invoice arrives

The data model

You buffer usage locally because Stripe Meters accept events asynchronously. Recording locally first lets you batch the reports, retry on failure, and show customers their running total without hitting the Stripe API on every page load.

-- Drizzle ORM schema
id           uuid primary key default gen_random_uuid()
user_id      uuid not null references users(id) on delete cascade
meter_name   text not null        -- matches the Stripe Meter event_name
quantity     integer not null default 1
reported_at  timestamptz          -- null until flushed to Stripe
created_at   timestamptz not null default now()

Every billable action inserts a row. The reported_at column stays null until the cron job flushes the batch.

Recording usage in the service layer

Before the billable action executes, call your usage service:

await usageService.record({
  userId: user.id,
  meterName: 'ai_messages',
  quantity: 1,
});

That is the entire product-side change. The billing logic is separate from the feature logic, which means you can add, remove, or reprice meters without touching the feature code.

Flushing to Stripe Meters

A Vercel cron job running every few minutes fetches unreported rows, groups them by customer and meter, and sends each group to Stripe:

await stripe.billing.meterEvents.create({
  event_name: 'ai_messages',
  payload: {
    stripe_customer_id: user.stripeCustomerId,
    value: String(totalQuantity),
  },
  timestamp: Math.floor(Date.now() / 1000),
});

After a successful report, you mark the rows with reported_at = now(). Stripe accumulates the events and adds them to the customer's invoice at period end. You never calculate amounts or create invoice line items manually.

Showing customers their usage

Nobody wants a surprise bill. A simple usage display keeps customers informed and reduces churn from sticker shock.

In a server component, query the current-period usage from your local table and show it as a number or a progress bar. Because you are reading from your own database, this is a fast query with no external API call. If the plan includes a free allowance before metered charges kick in, you can show how much of that allowance remains.

Stripe setup (three steps)

Before any of this works end-to-end, you need three things in the Stripe dashboard:

  1. A Meter -- Billing > Meters > Create. Set the event name to match what you pass in stripe.billing.meterEvents.create().
  2. A Price -- Create a recurring price with usage_type: metered and link it to the Meter.
  3. A Subscription -- When a customer subscribes, attach the metered price alongside any flat-rate prices. Stripe supports hybrid billing -- a base fee plus metered overage on the same subscription.

The Stripe dashboard guides you through each step. The code above is what connects your app to those settings.

Why this is faster than building it yourself

Calculating amounts, generating invoices, handling proration, retrying failed charges -- Stripe has solved all of that. Your job is to report what happened. Stripe's job is to charge for it correctly and handle every edge case in payment collection.

With this Next.js boilerplate you get the Drizzle ORM patterns, the service layer structure, the Stripe integration, and the background job setup already in place. Adding metered billing is a schema addition, a service call, and a cron route -- not a month of engineering work.

Get started today

If you are already running flat-rate Stripe subscriptions, you do not need to scrap your billing flow. Add a metered price to the existing subscription, start recording events, and let Stripe handle the rest.

Get the boilerplate and ship usage-based pricing this week.