---
title: "3.1.1 rejection: Restore Purchases in Expo · Designated Dev"
description: "Why Apple rejects apps without a working Restore Purchases, how to fix it with RevenueCat in Expo, and the store settings behind an empty paywall."
url: "https://designateddev.com/guidelines/3-1-1-restore-purchases/"
---

1.  [Home](/)
2.  [Guides](/guides/)
3.  App Review guidelines

Guideline 3.1.1

# Guideline 3.1.1 rejection: adding a working Restore Purchases to an Expo app with RevenueCat

Last verified September 27, 2026 by Designated Dev

## Short answer

Guideline 3.1.1 asks apps with in-app purchases to have a restore mechanism for anything that can be restored, such as subscriptions and unlocks. App Review looks for a Restore Purchases button and tests that it actually restores access. With RevenueCat in an Expo app, the button calls Purchases.restorePurchases() and checks the returned entitlement. It must be a development or production build, because Expo Go only runs RevenueCat's mock mode.

> Any credits or in-game currencies purchased via in-app purchase may not expire, and you should make sure you have a restore mechanism for any restorable in-app purchases.

[App Review Guideline 3.1.1, In-App Purchase](https://developer.apple.com/app-store/review/guidelines/)

## At a glance

| What you see | Why it happens | The fix |
| --- | --- | --- |
| Rejected: no “Restore Purchases” feature | The paywall has no restore button | A visible Restore Purchases button that calls restorePurchases() |
| Rejected: tapping Restore did nothing | The button shows an alert but never calls the SDK or checks the entitlement | Call restorePurchases(), check the entitlement, unlock, and tell the user the result |
| Paywall is empty for the reviewer | Products not submitted with the version, Paid Apps Agreement not active, or metadata incomplete | Fix App Store Connect first: agreement, product metadata, submit products with the version |
| Purchases never work in the preview | Expo Go runs RevenueCat in a mock mode | Test in a development build with sandbox accounts |

## What the reviewer does

The rejection is usually one of two things. Either there is no restore option at all, or there is one and it doesn’t work. RevenueCat’s community has examples of the second: the reviewer bought a subscription, reinstalled the app, tapped Restore, and “nothing happened.”

So restore has to do real work. It asks the store for the user’s purchases, checks what they’re entitled to, unlocks it, and tells the user what happened.

## The fix with RevenueCat in an Expo app

RevenueCat’s Expo guide is explicit: “To use and test RevenueCat with Expo, you’ll need to create an Expo development build.” In Expo Go the SDK runs a preview mode with mock responses, which is why purchases can look fine in a builder’s preview and fail everywhere else.

```
npx expo install react-native-purchases react-native-purchases-ui
```

Configure once, early in the app’s life, with the platform’s public SDK key:

```
import { useEffect } from "react";
import { Platform } from "react-native";
import Purchases from "react-native-purchases";

export function useConfigurePurchases() {
  useEffect(() => {
    Purchases.configure({ apiKey: Platform.OS === "ios" ? "appl_XXXX" : "goog_XXXX" });
  }, []);
}
```

Then the button. RevenueCat’s docs say `restorePurchases` “should only be called from some user interaction,” and it returns `CustomerInfo`, which you check for the entitlement:

```
import { Alert } from "react-native";
import Purchases from "react-native-purchases";

const ENTITLEMENT_ID = "pro";

// Only call this from the Restore Purchases button's onPress.
export async function onRestorePress(): Promise<boolean> {
  try {
    const customerInfo = await Purchases.restorePurchases();
    const active = typeof customerInfo.entitlements.active[ENTITLEMENT_ID] !== "undefined";
    Alert.alert(active ? "Purchases restored" : "No previous purchases were found for this Apple ID.");
    return active;
  } catch (e) {
    Alert.alert("Restore failed", "Please try again.");
    return false;
  }
}
```

A restore with nothing to restore still succeeds; the entitlement just isn’t active. Tell the user so. A silent tap is exactly what gets reported as “nothing happened.”

Put the button where a reviewer will look: on the paywall, and again in Settings. Neither Apple nor RevenueCat mandates a location, but a visible button on the paywall is what reviewers look for.

Don’t restore automatically at launch. Apple’s StoreKit documentation says “Don’t automatically restore purchases, especially when your app launches.” If you need a background sync, RevenueCat’s `syncPurchases` does it without the sign-in prompt, but read its warning about transferring anonymous users first.

## If the reviewer sees an empty paywall

That is a store configuration problem, not code, and it’s often cited under guideline 2.1 rather than 3.1.1. Check these in App Store Connect:

1.  **Paid Apps Agreement is active**, with banking and tax set up. Apple says it “must be Active to test In-App Purchases in the sandbox environment.”
2.  **The first product of each type is submitted with a new app version.** Apple requires the first consumable, non-consumable and subscription “of each type” to go in with a version.
3.  **Every product has complete metadata**, including the App Review screenshot. One recent rejection read: “you must provide an App Review screenshot in App Store Connect in order to submit in-app purchases for review.”
4.  **Bundle ID and keys match** between App Store Connect, RevenueCat and the app, and no StoreKit configuration file is left in the release scheme (from RevenueCat’s troubleshooting guide).
5.  **Every product you submit appears on the paywall** the reviewer sees. Guideline 2.1(b) asks for purchases that are “visible to the reviewer and functional.”

Changes to product metadata can take up to an hour to reach the sandbox, so wait before resubmitting.

## How we handle it

We test purchase, reinstall and restore on a real device with a sandbox account, the same cycle a reviewer runs, fix the restore flow and the entitlement check, and walk through App Store Connect before resubmitting so the reviewer never sees an empty paywall.

## Questions

### Do I need a Restore button if users have to log in?

Yes, keep one. RevenueCat recommends that all apps give users some way to trigger restorePurchases even when everyone has an account, and Apple asks for a restore mechanism for any restorable purchase.

### Can I restore purchases automatically when the app opens?

No. Apple's StoreKit documentation says not to restore automatically, especially at launch, and RevenueCat says restorePurchases should only follow a user action. For a silent sync, RevenueCat provides syncPurchases.

### Why is my paywall empty in review but fine on my phone?

Usually the in-app purchases weren't submitted with the app version, the Paid Apps Agreement isn't active, or a product is missing required metadata such as its App Review screenshot.

### Does Restore Purchases work in Expo Go?

No. In Expo Go, react-native-purchases runs a preview mode with mock responses, so real purchases and restores need a development or production build.

### Do consumable purchases need to be restorable?

The guideline covers restorable purchases. Consumables are used up; RevenueCat notes they can only be restored when you use your own App User IDs.

## Sources

1.  [Apple: App Review Guidelines, 3.1.1, 3.1.2 and 2.1(b)](https://developer.apple.com/app-store/review/guidelines/)
2.  [Apple: Restoring purchased products](https://developer.apple.com/documentation/storekit/restoring-purchased-products)
3.  [Apple: Overview for configuring in-app purchases](https://developer.apple.com/help/app-store-connect/configure-in-app-purchase-settings/overview-for-configuring-in-app-purchases/)
4.  [Apple: Submit an in-app purchase](https://developer.apple.com/help/app-store-connect/manage-submissions-to-app-review/submit-an-in-app-purchase)
5.  [RevenueCat: Restoring purchases](https://www.revenuecat.com/docs/getting-started/restoring-purchases)
6.  [RevenueCat: Installation with Expo](https://www.revenuecat.com/docs/getting-started/installation/expo)
7.  [RevenueCat: CustomerInfo and entitlements](https://www.revenuecat.com/docs/customers/customer-info)
8.  [RevenueCat: Troubleshooting offerings](https://www.revenuecat.com/docs/offerings/troubleshooting-offerings)
9.  [RevenueCat: App Store rejections](https://www.revenuecat.com/docs/test-and-launch/app-store-rejections)
10.  [Expo: In-app purchases](https://docs.expo.dev/guides/in-app-purchases/)

## Related guides

-   [Guideline 5.1.1(v)Guideline 5.1.1(v): adding in-app account deletion to an Expo and Supabase app](/guidelines/5-1-1-v-account-deletion/)
-   [Crash on launchWorks in Expo Go, crashes on launch in TestFlight: how to find and fix it](/guides/testflight-crash-on-launch-expo/)
-   [Guideline 5.1.2(i)Rejected under 5.1.2(i)? How to add AI data consent to an Expo app](/guidelines/5-1-2-i-ai-data-consent/)

Stuck on this in your own app?

## We fix it, test it on real devices, and resubmit.

30 minutes with the engineer who'd do the work. Bring the repo link or the rejection email. Fixed price once scoped, in writing.

[Book a call](https://cal.com/designateddev/intro)[Or tell us what's broken](https://designateddev.com/guidelines/3-1-1-restore-purchases/#send)

[Designated Dev](/)

[What breaks](/#found)[Process](/#process)[Pricing](/#pricing)[Guides](/guides/)[FAQ](/#faq)[Privacy](/privacy/)

[hello@designateddev.com](mailto:hello@designateddev.com)Updated Sep 27, 2026Live on both stores
