Skip to content

Convert Paid Apps to Freemium Without Affecting Existing Users

How to use StoreKit's AppTransaction API to transition from paid-up-front to freemium while preserving access for users who already paid.

The Paid-to-Freemium Transition Problem

Switching a paid app to freemium is a common business decision, but it comes with a fairness challenge: users who already paid for the app should not suddenly lose features or be asked to pay again. StoreKit’s AppTransaction API solves this cleanly.

Using AppTransaction to Check Purchase History

The key is AppTransaction.shared, which provides information about the original transaction for your app. Specifically, originalAppVersion tells you which version of the app the user originally downloaded. If that version was a paid version, you know the user already paid.

import StoreKit

func shouldGrantFullAccess() async -> Bool {
   do {
      let result = try await AppTransaction.shared
      if case .verified(let transaction) = result {
         let originalVersion = transaction.originalAppVersion
         // Version "2.0" was when the app went freemium
         if originalVersion.compare("2.0", options: .numeric) == .orderedAscending {
            return true // User purchased before freemium transition
         }
      }
   } catch {
      // Handle verification failure
   }
   return false
}

The logic is straightforward: compare the user’s originalAppVersion against the version where you made the freemium switch. If their original version predates the change, grant them full access automatically.

Important Details

The originalAppVersion corresponds to the CFBundleShortVersionString value at the time of the original purchase (or download for free apps). Make sure you know exactly which version number marks your transition point.

This approach requires no server infrastructure and no migration code. StoreKit handles verification through the App Store receipt chain, so the check is tamper-resistant. Apple documents this pattern in their original API for business model migration.

For TestFlight and simulator testing, originalAppVersion returns the CFBundleVersion (build number) instead, so plan your test cases accordingly.

Found this helpful? Follow me on Bluesky and Mastodon for more Swift tips and indie dev updates.