Skip to content
Tech Stack9 August 2026 · 8 min readUpdated 30 September 2026

Building Subscription-Gated Features in Flutter With RevenueCat in 2026

A Flutter RevenueCat subscription is a store transaction and an entitlement pretending to be one thing. How to gate on entitlements, handle offline use, and split trust between client and server.

Building Subscription-Gated Features in Flutter With RevenueCat in 2026

What you'll learn

  • What Does RevenueCat Give You Over inapppurchase?
  • How Do You Gate Features on RevenueCat Entitlements?
  • What Happens to a Flutter Subscription When the Device Goes Offline?
Article contents

You are adding a paywall to a Flutter app, and the screen is the easy part. The hard part is the single line that asks "is this person allowed?", because by launch it gets called from forty places and every one of them has to agree. Your first action: search your codebase for where you check access today, and count the distinct places.

A Flutter RevenueCat subscription is two systems wearing one coat: a store transaction owned by Apple or Google, and an entitlement your app decides to trust. The bugs live at the seam between them. My own BookBed property management SaaS bills through Stripe rather than in-app purchase, so I did not write this from a RevenueCat production account of my own. I wrote it from RevenueCat's documentation, package changelog and issue tracker, all read on 30 September 2026, because these rules move.

Below: what RevenueCat replaces, how to gate features so pricing can change without a migration, what happens offline, a race between webhook and client that is still an open GitHub issue, and the order I would ship it in.

What Does RevenueCat Give You Over in_app_purchase?

Three layered sheets of translucent tracing paper on a wooden desk, each inked with part of a wiring diagram, the top sheet curling away from a brass pin above a faint coffee ring

RevenueCat replaces receipt validation, cross-platform subscription state and the product-to-entitlement mapping that the in_app_purchase plugin leaves to your own backend. The rest of what it sells is analytics and experiments layered on that core.

The purchases_flutter package was at version 10.13.2 on pub.dev when I checked on 30 September 2026, published six days earlier. Here is how the options compare.

Concernin_app_purchaseRevenueCatRoll your own
Store transaction plumbingYesYesYou write it
Server-side receipt validationYou build itIncludedYou build it
Product to entitlement mappingNoneDashboard-configuredYour database
Cross-platform subscriber identityNoneapp_user_idYour auth system
CostFreeFree up to $2,500 MTR, then 1%Engineering time

The 1% deserves a second look before you commit. RevenueCat's pricing page says you pay nothing up to $2,500 in monthly tracked revenue, then 1% of what you track. The page does not spell out in the text I could read whether that revenue is counted before or after the store's cut, so confirm the definition with RevenueCat before you model it. At small scale the fee is noise. At tens of thousands in tracked revenue it is a line item you should have modelled.

One migration hazard matters before you add the dependency. According to the purchases_flutter changelog, version 10.0.0 moved to Google Play Billing Library 8.3.0, raised the minimum Android SDK from 21 to 23, and removed the workaround that let consumed one-time products be restored. The changelog warns that if your non-consumables are configured as consumables in the dashboard, customers cannot restore them from 10.0.0 onward. Check that mapping first.

How Do You Gate Features on RevenueCat Entitlements?

A single unlabelled brass key on a worn leather fob lit by a window, with a tangled ring of blank-tagged keys pushed back into the shadow behind it

Call Purchases.getCustomerInfo() and test whether your entitlement, such as pro, is active, and never branch on a product identifier: the entitlement lets any number of products grant the same access.

An entitlement is a named capability in the RevenueCat dashboard. Gate on it and you can add a Black Friday product, a regional price or a lifetime tier without shipping a build. Gate on a product ID and every pricing experiment becomes an app update.

Future<bool> hasPro() async {
  final info = await Purchases.getCustomerInfo();
  return info.entitlements.all['pro']?.isActive ?? false;
}

This mirrors the pattern in RevenueCat's quickstart, which checks entitlements.all[...] for isActive. The same page notes that the SDK caches the latest CustomerInfo whenever it updates, so in most cases the call pulls from the cache.

There is one place where product IDs legitimately appear in client code: the paywall itself, where you render an Offering and need prices. Even there, read the offering's packages so the dashboard stays in charge. The rule is narrower than "never". No product ID should sit inside a conditional that guards a feature.

If you sync entitlement state into a Firebase backend, the mechanics are in the Flutter, Firebase and RevenueCat stack guide.

What Happens to a Flutter Subscription When the Device Goes Offline?

A closed grey laptop on an airline tray table with a hard rectangle of window light falling across the lid, a face-down phone and a dried coffee ring beside it

The SDK serves the latest cached CustomerInfo, so a paying customer on a plane normally still passes your entitlement check without a network round trip.

The harder case is RevenueCat's own servers being unreachable. RevenueCat's Offline Entitlements announcement describes SDKs that keep processing purchases during an outage and sync when connectivity returns. On iOS it verifies current subscriptions through StoreKit 2, which means iOS 15.0 or later.

The same post lists limits. A fresh install needs one online session first, cross-platform purchases do not sync during an outage, consumable purchases are not supported offline, and subscriptions and non-consumables work offline on iOS only. Read the post again before you promise offline access on Android.

There is also a webhook you probably have not handled. Per the webhook event types documentation, RevenueCat sends TEMPORARY_ENTITLEMENT_GRANT when it cannot validate a purchase with the store, granting access for at most 24 hours. It later resolves into an INITIAL_PURCHASE or an EXPIRATION. If your backend books every grant event as a confirmed sale, you will record revenue that disappears a day later.

Does the Webhook Arrive Before the Client Sees the Entitlement?

Not necessarily, and nothing in the quickstart promises it. The client can hear about the purchase before your server does.

Issue #1094 on purchases-flutter is open and labelled as an enhancement. The reporter asks whether the webhook can be guaranteed to complete before the client is told about a new entitlement, because otherwise the app may call a server method that the server thinks the user should not have access to. As of 30 September 2026 that request is open, so I treat the ordering as unguaranteed.

That suggests a rule: split the trust. Client entitlement state is for rendering, such as hiding the upgrade button and showing the pro tab. Server-side entitlement state is for anything that costs money or exposes data, such as API quota, storage or a model call. Do not let the client's optimism authorise the server's spend, and design the server to answer "not yet" with a retry rather than a hard failure.

BookBed shows the same shape with a different vendor. Its case study describes Stripe checkout across browser tabs, using BroadcastChannel with a Firestore fallback so the original tab learns the outcome. Two systems on different clocks always force you to decide which one is allowed to be wrong for a few seconds.

Should you build restore purchases first?

Apple's App Review Guidelines say in section 3.1.1 that you should make sure you have a restore mechanism for any restorable in-app purchases. Treat it as required on iOS. Add one button that calls Purchases.restorePurchases() somewhere a frustrated person can find it fast, not buried three screens deep.

What does Family Sharing do to your metrics?

RevenueCat's Apple Family Sharing documentation says family-shared transactions are not included in charts data and are excluded from most overview metrics, with New Customers and Active Customers as the exceptions. It also notes a deliberate one-hour delay before a new shared subscription reaches family members, and that entitlements from family membership are revoked when a user leaves the family group.

Webhook payloads carry an is_family_share field, which the event documentation says is always false outside the App Store. If active customers climb while revenue looks flat, check that flag before you rewrite onboarding.

How should you design the paywall screen?

Show one screen after onboarding with free and paid side by side, one highlighted plan, and a button that names the outcome rather than the transaction. Where you place it matters more than how it looks.

The strategic choice sits above the design. RevenueCat's State of Subscription Apps 2026 report, covering more than 115,000 apps and $16 billion in revenue, reports a median Day-35 trial-to-paid conversion of 10.7% for hard paywalls against 2.1% for freemium, roughly a fivefold gap. It is a correlation across apps, not proof that switching your paywall type will produce that lift. A hard paywall also trades install volume for conversion rate, so model both before choosing.

For layout work, the Flutter developer tools I keep installed cover the debugging setup. Across the FlutterFlow marketplace templates I maintain, the sensible design choice is to keep the layout plain and make the trigger configurable, since placement is the variable you will want to change.

In what order would you ship it?

  1. Configure products in App Store Connect and Google Play Console first and get them into a reviewable state. Everything downstream blocks on this.
  2. Create entitlements in the RevenueCat dashboard, then attach products to them. Name entitlements after capabilities such as pro, never after price points. Use plain ASCII: a lookalike Unicode character in an identifier will not match.
  3. Add purchases_flutter and call Purchases.configure once at startup, before any UI that reads entitlement state.
  4. Log in your own user identifier with Purchases.logIn so the subscriber follows the account across devices.
  5. Write a single hasEntitlement(String key) helper and route every gate through it, so there is one place to add logging.
  6. Add restore purchases to settings and test it on a device that has never run the app.
  7. Point the webhook at your backend, verify each delivery using the method in RevenueCat's webhook documentation, and handle TEMPORARY_ENTITLEMENT_GRANT separately from INITIAL_PURCHASE.
  8. Enforce server-side entitlement checks on every paid API route before you switch the paywall on in production.

Steps 7 and 8 get postponed, and they are the only two that protect revenue rather than enable it. If you are building a subscription product at all, what counts as SaaS is worth a skim, because the billing model shapes the architecture more than the framework does.

Open your codebase and search for your entitlement key. How many distinct files came back, and does your server know about any of them?

Free resource

Free SaaS MVP Scope Template

A Notion document with the full feature checklist, MVP vs. nice-to-have table, pre-build questions, and cost signals — so you walk into any developer call knowing exactly what to ask for.

Get the template →
DL

Dusko Licanin

Full-Stack Developer · Banja Luka, Bosnia

Full-stack developer shipping SaaS MVPs, web apps, and mobile apps using AI-augmented workflows — without agency coordination overhead. Live portfolio: BookBed, Callidus, Pizzeria Bestek.

Frequently Asked Questions

How do you check RevenueCat entitlements in a Flutter app?

Call Purchases.getCustomerInfo() and check whether your entitlement is active in the returned entitlements. That single check is the whole gate, and it should live in one helper that every feature calls instead of being copied into widgets. RevenueCat's quickstart notes that the SDK caches the latest CustomerInfo whenever it updates, so in most cases the call pulls from the cache. Check the entitlement key, never the product identifier. Entitlements are configured in the RevenueCat dashboard and one entitlement can be granted by any number of products, which is what lets you change pricing without shipping a new build.

Should you use RevenueCat or the in_app_purchase plugin?

Use the in_app_purchase plugin if you already run a backend that validates App Store and Play receipts, and use RevenueCat if you would rather not build that layer. The difference is everything after the transaction: server-side validation, cross-platform subscriber identity, entitlement mapping and state that survives a reinstall. RevenueCat's pricing page lists a free tier up to $2,500 in monthly tracked revenue and then 1% of tracked revenue. Confirm how that revenue is defined with RevenueCat and model the percentage against your own engineering hours before deciding.

How do you design a Flutter paywall that converts?

Show one paywall right after onboarding, put free and paid side by side, highlight one plan and name the outcome in the button. Placement usually matters more than styling. RevenueCat's State of Subscription Apps 2026 report, covering more than 115,000 apps, reports a median Day-35 trial-to-paid conversion of 10.7% for hard paywalls against 2.1% for freemium. That is a comparison across apps, not a guaranteed lift for yours, and a hard paywall also trades install volume for conversion rate. Choose the trade deliberately and measure both numbers on your own app.

Do RevenueCat entitlements work offline in Flutter?

Yes, in the common case, because the SDK serves the latest cached CustomerInfo, so an existing subscriber keeps access without a network round trip. A separate Offline Entitlements feature covers the case where RevenueCat's own servers are unreachable. RevenueCat's announcement says it uses StoreKit 2 on iOS, requiring iOS 15.0 or later, that a fresh install needs one online session first, that consumables are not supported, and that subscriptions and non-consumables work offline on iOS only. Do not promise offline access on Android without testing it yourself.

What breaks if you gate features on product IDs instead of entitlements?

Every pricing change becomes an app update, because the identifiers your conditionals check are compiled into the binary. A Black Friday price, a regional tier, a lifetime option or a promo product each needs a new build before existing users benefit. Gate on an entitlement instead and the change happens in the RevenueCat dashboard, where you attach the new product to the same named capability. The one legitimate place for product identifiers in client code is the paywall, where you render prices, and even there you should read them from the offering's packages instead of hardcoding them.