DEV Community

Qtim
Qtim

Posted on

SaaS Pricing Before Launch: Plans, Limits, and Free Trials

Five architecture decisions that let a pricing change stay a product change, rather than turn into a release project.

A product manager moves Advanced Analytics from Pro to Business. In a pricing table, that is one line. In the product, plan checks may sit in the interface, API, background jobs, and support console. Updating the landing page takes an hour; releasing the new version can take weeks.

That gap makes monetization a development task every time the team wants to test a new offer. Experiments slow down, users receive different access in different parts of the service, and support has to explain why a payment succeeded while a feature remains locked.

I’m Anton Fokin, CEO of Qtim. We design SaaS platforms and separate the plan catalog, feature access, usage tracking, and billing from the start. Here are the decisions worth making before the first payment, so a new plan becomes a normal product change instead of a release project.

Keep permissions and limits separate from the plan name

A plan name rarely answers a technical question. “Business” might include 50 users, advanced analytics, SSO, and one year of data retention. A month later, its contents may change while some customers keep the terms they bought earlier.

The product needs a versioned catalog of products and prices. Each plan version records its effective period, currency, billing interval, entitlements, and limit rules. A customer subscription points to that version. Updating a public plan should not rewrite a paid customer’s terms after the fact.

The chain looks like this:

Customer or tenant → subscription → plan version → entitlements → limits → actual access

Feature access needs its own source of truth. Stripe, for example, maps products to entitlements and updates a customer’s active entitlements as a subscription changes. The same principle applies in an in-house system: the analytics module asks whether the customer has a particular entitlement. It does not need to know what marketing called the package on the landing page.

This adds another data layer to synchronize. In return, the rules live in one place, where the team can version, test, and change them without hunting through dozens of conditions in code.

Start every limit with an event you can recalculate

“Up to 10,000 operations per month” sounds like a finished rule. It is only the beginning. Does a retry count as a second operation? What happens to a cancelled job? Which time zone defines the start of a month? When does the customer see the remaining balance?

Usage-based billing needs a separate stream of events. At minimum, each event should contain:

  • the customer or tenant ID;
  • the measured action type;
  • a value and unit;
  • an event timestamp;
  • a unique key that prevents duplicate records;
  • the version of the rule used for calculation.

Stripe describes a similar lifecycle for usage billing: receive events, aggregate them, calculate charges, and monitor thresholds. Current totals can update asynchronously. The customer dashboard, billing database, and payment provider will therefore not always show the same number at the same moment.

Limits can also behave differently. A hard limit blocks an action. A soft limit permits overage and creates an additional charge. An informational limit only warns the customer. One product may use all three: an API stops at a threshold, storage allows a short buffer, and an employee count moves the account to the next package.

Fourteen days mean little without a first-value moment

In a ChartMogul and ProductLed survey of 200 B2B products, 57% used a free trial and 62% of those trials lasted 14 days. The median free-to-paid conversion rate was 8%. Those figures describe that sample; they are not a benchmark for every SaaS product.

Trial length should follow the path to first value. An analytics service may show a useful result after the customer connects a data source and imports data. An enterprise platform may need team invitations, role setup, and security approval first. The same 14 days create very different chances of activation in those two cases.

A trial needs its own state model. The system should know:

  • when the trial starts and ends;
  • which features and limits apply during it;
  • whether a payment card is required;
  • what happens at the end: payment, pause, restricted access, or cancellation;
  • whether the trial can be extended, and who can approve it.

Before launch, define the analytics events as well: registration, trial start, the key action, the first-value moment, payment-details entry, payment, and cancellation. This shows where interest turns into friction. One conversion number cannot explain that path.

An upgrade takes seconds, its effects last through the billing period

The “Move to Business” button starts a chain of decisions. The product can grant new entitlements at once and prorate the additional charge for the remaining days. A downgrade often takes effect in the next billing period so the customer keeps access they have already paid for. A failed payment may first trigger a grace period, then restricted access, and only then a block.

Model these as explicit states and events: subscription activation, plan change, successful or failed payment, pause, and period end. Event handlers can receive the same notification more than once, so entitlement changes and charges need to be idempotent.

Payment status and current access also need separate treatment. The provider tracks money; the product decides what the user can do now. A delivery delay, duplicate webhook, or temporary failure can separate those states. Keep an event log, retry failed processing, and reconcile subscriptions, invoices, and active entitlements on a schedule.

This is how ScanWow, a 3D-model platform we developed, works. Its plan defines a monthly download limit and a set of entitlements, while PayPal sends payment statuses through webhooks. When a customer turns off auto-renewal, access remains available through the paid period. The account then returns to Free automatically.

Support needs a view of this model too. A support specialist should see the plan version, current usage, transition history, and the reason for a restriction. A temporary entitlement or credit needs an expiry date and the name of the person who made the change. Otherwise, help for one customer becomes an exception that nobody remembers to remove.

Five decisions to make before the first payment

  1. Value unit. What does the customer pay for: a seat, document, processed operation, data volume, or a computation result?
  2. Plan version. Which prices, billing intervals, entitlements, and limits apply together? When does the version take effect, and what happens to existing subscriptions?
  3. Access model. Where does the entitlement set live, and which service makes the final access decision?
  4. Transitions. How do trials, upgrades, downgrades, pauses, overdue payments, cancellations, and refunds work?
  5. Verification. Which events enter analytics and the audit trail? How will the system reconcile calculations, and what will support see when states disagree?

This list does not require the team to predict the perfect price. It creates the places where a price and package can change without rewriting the product. At the start, one workable model is enough if its boundaries are explicit.

Where configuration ends and development begins

Price, package contents, limits, and an effective date stay within the product model. A new release is needed when the product introduces a different usage unit, changes the charging method, or adds a new access scenario. That boundary shows how flexibly the monetization model was designed.

Before development starts, define the value unit and subscription states. Then build the catalog, entitlements, limits, and billing around them.

If you want to test that boundary before launch, talk to us. We can map plans, limits, trials, and transitions into the architecture of your SaaS platform.

Top comments (7)

Collapse
 
posting-dude profile image
Posting Dude •

the line about a temporary entitlement needing an expiry date and the name of whoever granted it is the one small teams skip, and then a "just for this customer" unlock quietly lives forever. the hard / soft / informational limit split is useful too, most people only ever think of the hard block. for a solo founder before the first sale though, would you still build the versioned plan catalog on day one, or is a single plan plus an entitlements table enough until the first real price change?

Collapse
 
qtim profile image
Qtim • • Edited

Honest answer: no, you don’t need a full catalog with an admin UI before the first sale. But two things are worth having on day one, because they cost an hour now and a migration later. First, a plan_version_id on the subscription, even if you only have version 1. Second, code that checks entitlements, not plan names. A single plan plus an entitlements table is enough, as long as nothing in the code says if plan == "pro". When the first real price change comes, you add version 2 and existing customers stay on version 1 without any special handling. That’s what we meant by “one workable model is enough if its boundaries are explicit.” And for the “just for this customer” unlocks, a nightly job that expires grants past their date is enough to keep them from living forever.

Collapse
 
posting-dude profile image
Posting Dude •

a plan_version_id even with only one version, and nothing in the code saying plan == "pro", is cheap insurance. that second one is the bit people skip and then grep for later. and a nightly job that expires grants is way less ceremony than an admin screen. thanks for the detailed answer.

Collapse
 
veristria profile image
StackShortlist •

Pointing the subscription at a plan version instead of the plan name is the part most early teams skip, and it bites the first time someone on an old deal asks why a feature disappeared. The idempotency key on usage events is the other one. Retries double counting is a support ticket you only want to debug once.

Collapse
 
qtim profile image
Qtim •

Exactly, and both problems show up late: the first one when a customer on an old deal opens a ticket, the second at the first month-end reconciliation. One detail on the idempotency key that’s easy to miss: generate it where the action happens (job ID, request ID), not when the event is ingested. Otherwise each retry arrives with a fresh key and the dedup does nothing. That’s also why we suggest storing the rule version on every usage event: a disputed total can then be recalculated exactly as it was billed, even after the limits have changed.

Collapse
 
elijahbrown profile image
Elijah Brown •

One thing I'd add to the trial state model: whether this person has already had a trial. If the only key is the account row, the trial resets every time someone signs up again with a new address, and on a product with real compute costs that adds up quickly. I'd key the claim on a normalised email (lowercased, Gmail dots and plus tags folded) with a unique index, so a second claim fails in the database rather than depending on app code. It won't stop someone with a genuinely fresh inbox, but it closes the cheap version of the loop, and the trial-to-paid numbers you describe get a lot cleaner once repeat trials aren't mixed in.

Collapse
 
qtim profile image
Qtim •

Great addition, and it fits the trial state model well: eligibility belongs to a separate record, not to the account row. A unique index on a normalised email is a solid baseline. One caveat: fold dots only for Gmail-style domains, because on other providers they can be meaningful. If the trial requires a card, the payment provider’s card fingerprint is a second strong key; Stripe, for example, exposes one. For B2B, a check on the company domain (excluding public ones like gmail.com) catches teams that rotate employees through the trial. And you’re right about the analytics: it’s worth recording a repeat claim as its own event, so it stays out of trial-to-paid conversion instead of quietly diluting it.