Skip to main content
Migrate Already selling? Move your customers to Keylight without re-issuing a single key.
Keylight
Blog
pricing trials free-tier

Free Tier, Trial, or Paid: Designing Mac App Licensing Tiers

8 min read Nicolas Demanez — Founder

Most Mac apps launch with a trial, discover a free tier might help, and then face a real question: what does “free” even mean in the licensing model, and how do the states relate? The answer comes down to three distinct configurations — and choosing the wrong one is not a pricing mistake so much as a licensing mistake.

The three tiers Keylight actually models

Keylight’s License Lifecycle v2 defines six LicenseState cases. Three of them map to the concepts developers are usually weighing before launch.

Free tier is keyless. No customer action required — no key purchase, no activation. When freeTierEnabled: true is set on the factory, the SDK resolves to .freeTier for any user whose trial has ended (or never started) and who has not activated a paid key. The Keylight dashboard tracks free-tier device counts via an anonymous instance UUID and a debounced heartbeat, so you can watch your free base and its conversion rate without building any analytics yourself.

Trial is time-bound. The trialDurationDays parameter on Keylight.manager(...) sets the evaluation window, and the SDK tracks the start timestamp locally. During the trial, licensing.state resolves to .trial(daysLeft:) — your app can surface a banner that tightens as the number drops. When the window closes, the SDK resolves either to .freeTier (if freeTierEnabled: true) or to .expired (if not). There is no separate trial backend; it runs entirely on-device.

Paid is a signed lease issued when the customer goes through your Stripe checkout. Keylight mints an Ed25519-signed license, delivers it by email, and the SDK picks it up on the next revalidation. licensing.state flips to .licensed. Paid licenses are activation-limited (a device count per tier) and can be subscription-bound — in which case the expiry tracks the subscription period, not a fixed duration.

The configuration that covers all three at once:

let manager = try Keylight.manager(
    sdkKey: "sdk_live_...",
    tenantId: "acme",
    productId: "widget",
    keyPrefix: "ACME",
    trustedPublicKeyBase64: "...",
    trialDurationDays: 14,         // required — pick 7, 14, or 30 (see below)
    freeTierEnabled: true,         // optional — defaults to false
    branding: .init(/* ... */)
)

Two things worth noting precisely. trialDurationDays is a required parameter — the SDK has no default. You pick the number. freeTierEnabled defaults to false — the factory will compile and run correctly without it, and without it there is no free tier. There is also a runtime counterpart: productFreeTierEnabled() on LicenseProvider, which reads from the cached server-side product configuration. The factory parameter is your local declaration; the runtime method is the server’s authoritative read of the same flag. Both are real, and both can be true at once.

When a free tier helps you, and when it just adds support load

A free tier is not a trial with an infinite clock. It is a different product, delivered to a different customer: someone who may never pay, by design. That distinction changes the calculus.

A free tier earns its keep in a few situations. Network effects are the clearest: if your app becomes more useful when more people use it, getting those people into the app for free is direct product investment. Audience building works similarly — if you are planning a paid product and want an installed base first, free users today are potential buyers tomorrow. Natural low-support paths help too: a keyless free user has no activation, no key management, fewer ways to generate a ticket. If the free experience is simple enough that it rarely breaks, the incremental support cost is low.

A free tier hurts in equally clear situations. Free replacing paid is the most common trap: if the free version covers the reason someone would buy, the paid tier is not obvious. Not every app has a natural split between a genuinely useful free mode and a meaningfully better paid mode. Confused users are the second trap: customers who do not realize they are in free mode, or who expect paid-tier behavior from a free account, show up in support queues in ways that trial users do not. A trial has a clock and a purchase prompt — the arc is clear. A free tier with no clear differentiation just creates ambient confusion. Free-tier traffic with real cost is the third: if your app’s free usage hits a backend with real per-request cost, a large free base can quietly run expensive.

The honest test: can you describe what the free user gets and what they would have to pay for, in one sentence, in a way that makes both feel fair? If you cannot, the free tier will cost you more in confusion than it returns in conversions.

Choosing trial length: 7, 14, or 30 days

trialDurationDays has no default. You choose it, and the SDK enforces it identically regardless of what you pick. Here is how to reason about the number.

Seven days is the urgency play. The customer knows the window is short, so they evaluate now rather than letting the app sit. This works for apps whose value is obvious in a session or two — a focused utility, a single-purpose tool. The risk is cutting off users who have not reached the moment the app proves itself. If a typical workflow takes more than two or three sessions to set up, a seven-day trial might expire before the customer has seen what they paid for. Conversions often cluster at the end of the trial under pressure, not after genuine evaluation.

Fourteen days is the safe default. It gives two weekends — enough time to actually try the app in a real context, across real tasks, without being so long that the deadline disappears from view. Most productivity and developer tools land here. It is not a universal answer, but it is the right starting point for most apps that have not measured anything yet.

Thirty days makes sense for apps with weekly workflows. If the main value case — a sync, a report, a project review — happens roughly once a week, a fourteen-day trial might only give the customer two chances to trigger it. A thirty-day window guarantees four. The tradeoff is deadline pressure: thirty days is long enough that some evaluators defer the decision indefinitely. If your conversion data shows most conversions happening in the last two days of the trial, a longer trial is not helping — it is adding indecision.

Pick the number once, ship, and actually measure where conversions cluster. The SDK makes it trivial to change later. For more on conversion timing and reminder design, see the trial-to-paid conversion post — it covers in-app prompt timing in detail.

How upgrades, downgrades, and lapses work between tiers

Tier transitions are where most developers have questions, because the SDK state and the customer flow both need to be correct.

Free tier → Paid. The customer goes to your purchase URL (set in BrandingConfig.purchaseURL), completes the Stripe checkout, and Keylight mints a signed lease. On the next SDK revalidation — typically triggered on app foreground or launch — licensing.state flips from .freeTier to .licensed. Local app data is untouched because the customer identity (their email, their anonymous free-tier instance UUID) links the tiers at the server level. No migration; the entitlements just change. The in-app upgrade UI — rendering the purchase flow inside your app — is on the roadmap and not shipped yet. Today the path is the customer opening your purchase URL in a browser.

Trial → Paid. Same flow. The customer buys, Keylight delivers the key by email, the customer activates it, and the SDK swaps .trial.licensed. Or, if the checkout carries the right metadata, Keylight activates automatically and the state flips on the next foreground check.

Trial → Free (lapse with freeTierEnabled: true). This is the state transition most developers do not think through until it bites them. When the trial clock runs out and freeTierEnabled: true is set, the SDK resolves to .freeTier instead of .expired. The user keeps using the app in the free-tier experience, and the purchase prompt should reflect that — not “your trial expired” but “you are on the free plan.” These are different messages. Build the distinction into your state switch.

Trial → Expired (lapse without free tier). When freeTierEnabled is false (the default), an expired trial resolves to .expired. The app should show a purchase prompt. For the expiry screen design, the trial-to-paid conversion post covers the highest-leverage moment in the funnel.

Paid → Lapsed (subscription end). A subscription-type license has an expiresAt driven by the subscription period. When the subscription ends and the grace window closes, the lease expires. If the key type has fallbackAccess: true, the SDK resolves to .limited — the lapsed customer keeps using a reduced mode (typically read-only or data export) so they never lose access to their data. If fallbackAccess is false, the SDK resolves to .expired. Renewal mints a fresh lease, and the same key revives.

For all paid upgrades between tiers — Personal to Team, for example — the current path is: customer goes to the purchase URL, the upgrade checkout carries their existing key in the metadata, Keylight swaps the key type in place (same key string, new entitlements), and the SDK picks up the new tier on the next revalidation. For a breakdown of how the Stripe webhook pipeline handles the payment-to-license step, see how the webhook pipeline works. For how to think about which paid tiers to offer in the first place, see indie Mac app pricing in 2026 and the pricing page.

Getting these transitions right is mostly about being precise: not conflating .freeTier (never paid) with .limited (lapsed paying customer), and not assuming the SDK will handle a state you did not configure. The features/trials page has the reference for how trial state is read and how conversion works on the SDK side.

If you are designing the tier structure for a new app or rethinking one that is already live, we would like to hear what is working and what is not — send us your feedback and we will keep this post updated as the in-app upgrade UI ships.

Frequently asked

Should my Mac app have a free tier or just a trial?+

A free tier earns its keep when the free version produces sustained engagement that converts later — and when the support cost stays low. A time-bound trial is the right default for productivity apps where the value is immediate and the decision needs a deadline.

How long should a Mac app trial be?+

Seven days is too short for any app that takes more than one session to evaluate. Fourteen days is a safe default. Thirty days makes sense for apps with weekly workflows. Pick the length when you call Keylight.manager — it is a required parameter.

Can a customer upgrade from a free tier to a paid plan without losing data?+

Yes. Keylight tracks the customer identity across tiers, so an upgrade swaps the license without touching local app data. The next online revalidation picks up the new entitlements.

Ready to ship?

Create your account and start licensing your apps in under a minute. Free forever tier included.

Start Free