← Writing

Changing prices in Stripe without breaking existing subscriptions

Stripe Prices are immutable — you don't edit them, you replace them. Here's the repricing pattern that keeps every existing agreement intact: new Prices, archived old ones, and contract versioning with pricing snapshots so history stays auditable.

On this page

Sooner or later every subscription business changes its prices, and that’s when a Stripe design decision most people never noticed becomes the whole project: Prices are immutable. You cannot edit the amount on an existing Price object. Repricing in Stripe is not an update — it’s a replacement, and everything interesting follows from that.

I wrote up the sharp edge itself in a short TIL; this is the longer version — the pattern I used in a repricing rework for a device-protection platform client, where the business model itself was changing shape (from a payment-credit model to a monthly coverage fee plus per-claim charges), so “just change the number” was never on the table.

Why immutability is a feature you’re glad of later

The immutability rule feels like friction the first time it blocks you. It’s actually protecting the property a billing system lives or dies by: every historical charge remains explainable. If Prices were editable, an invoice from last March would reference a Price whose amount might since have changed — and your billing history would quietly stop meaning anything.

So Stripe’s model is: a Product is the thing you sell; Prices are the immutable terms it has been sold at, accumulating over time. A price change grows this history; it never rewrites it.

The mechanical recipe

The Stripe-side moves for a price change:

  1. Create the new Price on the existing Product — same product identity, new terms. Don’t create a new Product per price change; the product didn’t change, its price did.
  2. Archive the old Price (set it inactive). Archiving prevents new checkouts and subscriptions from using it — while every subscription already attached to it continues billing, untouched.
  3. Point all creation paths at the new Price — checkout sessions, your pricing page, any hardcoded price IDs in configuration. Price IDs in config, not code, makes this step a deploy-free change.
  4. Decide, explicitly, what happens to existing subscribers. This is a business decision wearing an engineering costume:
    • Grandfather them — do nothing; they keep their old Price forever. Stripe’s default behavior makes this the zero-effort option, which is why it’s so common.
    • Migrate them — update each subscription to the new Price, with whatever notice and proration policy you’ve chosen. This is an explicit, scriptable migration you own end to end.

The part Stripe can’t do for you: contract versioning

Here’s the deeper pattern from the device-protection rework, and it’s the piece I’d carry into any billing system regardless of provider.

When that platform’s model changed — monthly coverage fee plus claim charges, replacing the old credit model — the question wasn’t only “what’s the new price?” It was: what did each existing customer agree to, and what governs them now? The answer we built:

Version the contract, and snapshot the pricing into it. Every agreement row in the database carries the pricing terms it was sold under — copied in at signing time, not looked up live. New pricing means a new contract version; existing agreements keep their snapshot, permanently. The application never asks “what’s the current price?” when billing an existing agreement — it asks “what does this agreement say?”

Alongside it: a typed payment ledger — every money movement recorded as an immutable, categorized entry (fee, claim charge, adjustment), so any customer’s financial history reads as a sequence of explicit typed facts rather than a mutable balance.

Notice what this buys:

  • Support can answer “why was I charged this?” from your own data, without Stripe-dashboard archaeology.
  • Repricing becomes an ordinary event, not a migration crisis — new version, new snapshot; old agreements unaffected by construction.
  • Provider independence — your source of truth about what customers agreed to lives in your database. Stripe executes the billing; it doesn’t define the deal.

The symmetry is worth appreciating: this is the same immutability Stripe enforces on Prices, extended into your own domain model. Stripe’s Prices are immutable so charges stay explainable; your contract snapshots are immutable so agreements stay explainable. Mutable pricing state is how businesses end up unable to say what a customer is actually owed.

The checklist

  • Never model a price change as an edit. New Price, archive old.
  • Route all new business to the new Price; let attached subscriptions be until you decide otherwise.
  • Make grandfather-vs-migrate an explicit, recorded decision.
  • Snapshot terms into agreements at signing; version contracts when terms change.
  • Record money movements as typed, immutable ledger entries.

Repricing is one of those jobs that’s dangerous exactly once — the first time, when the temptation is to make it small. Modeled properly, every future price change is boring. That’s the goal: boring.

← All writing Book a call →
Book a call → WhatsApp