HomeiOS DevelopmentMigrating an iOS app from Paid up Entrance to Freemium – Donny...

Migrating an iOS app from Paid up Entrance to Freemium – Donny Wals


Revealed on: January 30, 2026

Paid up entrance apps generally is a robust promote on the App Retailer. You is perhaps getting loads of views in your product web page, but when these views aren’t changing to downloads, one thing has to alter. That is precisely the place I discovered myself with Maxine: first rate site visitors, nearly no gross sales.

So I made the swap to freemium, although I did not actually need to. Ultimately, the info was fairly apparent and I have been getting suggestions from different devs too. Free downloads with elective in-app purchases convert higher and get extra customers by way of the door. After excited about the easiest way to make the swap, I made a decision that present customers get lifetime entry at no cost, and new customers get 5 exercises earlier than they should subscribe or unlock a lifetime subscription. That ought to give them loads of time to correctly try to take a look at the app earlier than they commit to purchasing.

On this publish, we’ll discover the next matters:

  • Learn how to grandfather in present customers utilizing StoreKit receipt information
  • Testing gotchas you will run into and methods to work round them
  • The discharge sequence that ensures a clean transition

By the tip, you will know methods to migrate your individual paid app to freemium with out leaving your loyal early adopters behind.

Grandfathering in customers by way of StoreKit

No matter the way you implement in-app purchases, you should use StoreKit to test when a person first put in your app. This allows you to determine customers who paid for the app earlier than it went free and mechanically grant them lifetime entry.

You are able to do this utilizing the AppTransaction API in StoreKit. It offers you entry to the unique app model and unique buy date for the present system. It is a fairly good method to detect customers which have purchased your app pre-freemium.

Here is methods to test the primary put in model (which is what I did for Maxine):

import StoreKit

func isLegacyPaidUser() async -> Bool {
  do {
    let appTransaction = strive await AppTransaction.shared

    swap appTransaction {
    case .verified(let transaction):
      // The model string from the primary set up
      let originalVersion = transaction.originalAppVersion

      // Examine in opposition to your final paid model
      // For instance, if model 2.0 was your first free launch
      if let model = Double(originalVersion), model 

Since this logic may probably trigger you lacking out on income, I extremely advocate writing a few unit checks to make sure your legacy checks work as meant. My method to testing the legacy test concerned having a technique that might take the model string from AppTransaction and test it in opposition to my goal model. That approach I do know that my take a look at is stable. I additionally made positive to have checks like ensuring that customers that have been marked professional as a consequence of model numbering have been capable of cross all checks accomplished in my ProAccess helper. For instance, by checking that they are allowed to begin a brand new exercise.

If you wish to be taught extra about Swift Testing, I’ve a few posts within the Testing class that will help you get began.

I opted to go for model checking, however you possibly can additionally use the unique buy date if that matches your scenario higher:

import StoreKit

func isLegacyPaidUser(cutoffDate: Date) async -> Bool {
  do {
    let appTransaction = strive await AppTransaction.shared

    swap appTransaction {
    case .verified(let transaction):
      // When the person first put in (bought) the app
      let originalPurchaseDate = transaction.originalPurchaseDate

      // In the event that they put in earlier than your freemium launch date, they're legacy
      return originalPurchaseDate 

Once more, in the event you resolve to ship an answer like this I extremely advocate that you just add some unit checks to keep away from errors that would price you income.

The model method works nicely when you will have clear model boundaries. The date method is helpful in the event you’re unsure which model quantity will ship or in order for you extra flexibility.

As soon as you’ve got decided the person’s standing, you will need to persist it domestically so you do not have to test the receipt each time:

import StoreKit

actor EntitlementManager {
  static let shared = EntitlementManager()

  non-public let defaults = UserDefaults.commonplace
  non-public let legacyUserKey = "isLegacyProUser"

  var hasLifetimeAccess: Bool {
    defaults.bool(forKey: legacyUserKey)
  }

  func checkAndCacheLegacyStatus() async {
    // Solely test if we've not already decided standing
    guard !defaults.bool(forKey: legacyUserKey) else { return }

    let isLegacy = await isLegacyPaidUser()
    if isLegacy {
      defaults.set(true, forKey: legacyUserKey)
    }
  }

  non-public func isLegacyPaidUser() async -> Bool {
    do {
      let appTransaction = strive await AppTransaction.shared

      swap appTransaction {
      case .verified(let transaction):
        if let model = Double(transaction.originalAppVersion), model 

My app is a single-device app, so I haven’t got multi-device eventualities to fret about. In case your app syncs information throughout gadgets, you may want a extra concerned resolution. For instance, you possibly can retailer a “legacy professional” marker in CloudKit or in your server so the entitlement follows the person’s iCloud account quite than being tied to a single system.

Additionally, storing in UserDefaults is a considerably naive method. Relying in your minimal OS model, you may run your app in a probably jailbroken atmosphere; this is able to enable customers to tamper with UserDefaults fairly simply and it could be way more safe to retailer this info within the keychain, or to test your receipt each time as an alternative. For simplicity I am utilizing UserDefaults on this publish, however I like to recommend you make a correct safety danger evaluation on which method works for you.

With this code in place, you are all set as much as begin testing…

Testing gotchas

Testing receipt-based grandfathering has some quirks you must find out about earlier than you ship.

TestFlight at all times reviews model 1.0

When your app runs through TestFlight it runs in a sandboxed atmosphere and AppTransaction.originalAppVersion returns "1.0" no matter which construct the tester really put in. This makes it unimaginable to check version-based logic by way of TestFlight alone.

You will get round this utilizing debug builds with a handbook toggle that allows you to simulate being a legacy person. Add a hidden debug menu or use launch arguments to override the legacy test throughout improvement.

#if DEBUG
var debugOverrideLegacyUser: Bool? = nil
#endif

func isLegacyPaidUser() async -> Bool {
  #if DEBUG
  if let override = debugOverrideLegacyUser {
    return override
  }
  #endif

  // Regular receipt-based test...
}

Reinstalls reset the unique model.

If a person deletes and reinstalls your app, the originalAppVersion displays the model they reinstalled, not their very first set up. It is a limitation of on-device receipt information. In case you’ve written the person’s pro-status to the keychain, you’d really be capable to pull the professional standing from there.

Sadly I have never discovered a fail-proof method to get round reinstalls and receipts resetting. For my app, that is acceptable. I haven’t got that many customers so I feel we’ll be okay when it comes to danger of somebody dropping their legacy professional entry.

Machine clock manipulation.

Customers with incorrect system clocks may work their approach round your date-based checks. That is why I went with version-based checking however once more, it is all a matter of figuring out what an appropriate danger is for you and your app.

Making the transfer

Once you’re able to launch, the sequence issues. Here is what I did:

  1. Set your app to handbook launch. In App Retailer Join, configure your new model for handbook launch quite than automated. This offers you management over timing.

  2. Add a be aware for App Assessment. Within the reviewer notes, clarify that you will swap the app’s value to free earlier than releasing. One thing like: “This replace transitions the app from paid to freemium. I’ll change the worth to free in App Retailer Join earlier than releasing this model to make sure a clean transition for customers.”

  3. Await approval. Let App Assessment approve your construct whereas it is nonetheless technically a paid app.

  4. Make the app free first. As soon as permitted, go to App Retailer Join and alter your app’s value to free (or arrange your freemium pricing tiers).

  5. Then launch. After the worth change is dwell, manually launch your permitted construct.

I am not 100% positive the order issues, however making the app free earlier than releasing felt just like the most secure method. It ensures that the second customers can obtain your new freemium model, they are not by chance charged for the previous paid mannequin.

In Abstract

Grandfathering paid customers when switching to freemium comes all the way down to checking AppTransaction for the unique set up model or date. Cache the consequence domestically, and think about CloudKit or server-side storage in the event you want cross-device entitlements.

Testing is hard as a result of TestFlight at all times reviews model 1.0 and sandbox receipts do not completely mirror manufacturing. Use debug toggles and, ideally, an actual system with an older App Retailer construct for thorough testing.

Once you launch, set your construct to handbook launch, add a be aware for App Assessment explaining the transition, then make the app free earlier than you faucet the discharge button.

Altering your monetization technique can really feel like admitting defeat, however it’s actually simply iteration. The App Retailer is aggressive, person expectations shift, and what labored at launch won’t work six months later. Take note of your conversion information, be keen to adapt, and do not let sunk-cost considering hold you caught with a mannequin that is not serving your customers or your online business.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments