What This Error Means
0:03:00When RevenueCat reports "no products found", "empty offerings", or "Error fetching offerings", it means one specific thing: the SDK reached the RevenueCat backend and fetched your offering configuration, but somewhere along the chain the product identifiers could not be resolved into real, purchasable store products. The result is a nil current offering or an offering with zero availablePackages, and a paywall with nothing on it.
This is, by a wide margin, the most common RevenueCat issue. The good news: it is always a configuration problem with a finite list of causes, and every one of them is covered on this page. To fix it, you need to understand the chain the SDK walks every time you call getOfferings():
Store product (App Store / Google Play) ← must exist, be active/approved & available
↓ product ID must match character-for-character
RevenueCat Product (Product Catalog → Products) ← must exist for YOUR platform
↓ must be attached to a package
Package (e.g. $rc_monthly) ← must contain a product for YOUR store
↓ must belong to an offering
Offering (e.g. "default") ← must be marked Current
↓ fetched via your public API key (appl_… / goog_…)
Your app ← bundle ID / package name must match
↓ device store must be able to serve the products
The device ← agreements signed, app published, tester set up
If any single link in that chain is broken, you get empty offerings. The error message usually tells you which half of the chain to look at: RevenueCat's side or the store's side.
The Error Message Zoo
All of the following are the same family of problem. Find yours:
# iOS: the classic
[RevenueCat] 🍎‼️ Error fetching offerings - The operation couldn't be completed.
There's a problem with your configuration. None of the products registered in the
RevenueCat dashboard could be fetched from App Store Connect (or the StoreKit
Configuration file if one is being used).
More information: https://rev.cat/why-are-offerings-empty
# Android: platform mismatch (ConfigurationError)
[RevenueCat] 😿‼️ Error fetching offerings -
PurchasesError(code=ConfigurationError, underlyingErrorMessage=You have configured
the SDK with a Play Store API key, but there are no Play Store products registered
in the RevenueCat dashboard for your offerings. If you don't want to use the
offerings system, you can safely ignore this message. To configure offerings and
their products, follow the instructions in https://rev.cat/how-to-configure-offerings.
More information: https://rev.cat/why-are-offerings-empty
# iOS: products exist but were never submitted
WARN: ⚠️ RevenueCat SDK is configured correctly, but contains some issues…
⚠️ monthly (monthly): This product's status (READY_TO_SUBMIT) requires you to
take action in App Store Connect before using it in production purchases.
# Android: store can't serve the SKU
BillingClient: Product not found - SKU is not available for purchase
BillingResponseCode: ITEM_UNAVAILABLE (4)
# Both: silent variant (no error at all)
offerings.current == nil // or
offerings.current.availablePackages.count == 0
# Both: network family (different root cause: connectivity, not configuration)
Error: Unable to fetch offerings: NetworkError / Request timed out
5-Minute Triage
0:03:00Before checking anything else, enable debug logging, since every diagnosis below starts from what the log says:
Swift: Purchases.logLevel = .debug (before configure)
Kotlin: Purchases.logLevel = LogLevel.DEBUG (before configure)
React Native: Purchases.setLogLevel(LOG_LEVEL.DEBUG)
Flutter: await Purchases.setLogLevel(LogLevel.debug)
Then match your symptom in this table. Each row links to the section with the verified fix:
| Your symptom | Most likely cause | Go to |
|---|---|---|
Log says ConfigurationError… Play Store API key, but there are no Play Store products registered (or the App Store equivalent) | Your offering only contains products for the other store | Dashboard Fixes, cause D |
offerings.current is nil, no store error in the log | No offering marked Current, or no packages/products in it | Dashboard Fixes, causes A–C |
| "None of the products registered… could be fetched", iOS, first integration ever | Paid Applications Agreement not Active, or Simulator without a StoreKit Configuration file | iOS Fixes, causes 1 & 6 |
Log warns READY_TO_SUBMIT / products show "Missing Metadata" | Products incomplete or never submitted with a build | iOS Fixes, causes 2 & 3 |
RevenueCat REST API / dashboard shows everything correct, but availablePackages is 0 on iOS | No binary ever uploaded, so StoreKit hasn't activated your product IDs | iOS Fixes, cause 4 |
| Works for you, fails for users in another country | Product availability limited to specific countries/regions | iOS Fixes, cause 7 / Android Fixes, cause 6 |
Android: ITEM_UNAVAILABLE or empty offerings on a real device | App not on a testing track, tester not opted in, or installed build isn't from Play | Android Fixes, causes 1–3 |
| Android: dashboard shows credential warnings, or setup was < 36h ago | Play service credentials invalid or still propagating | Android Fixes, cause 5 |
| React Native in Expo Go: native module missing / empty products | Expo Go can't run native modules, so you need a dev build | Cross-Platform |
getProducts() works but getOfferings() is empty | Break is between product and offering in the dashboard | Dashboard Fixes |
| Worked yesterday, empty today, nothing changed | Store/cache propagation, or someone edited the offering | Still Stuck? |
NetworkError / timeout | Connectivity, VPN/firewall, or (rarely) service disruption, not configuration | Still Stuck? |
RevenueCat Dashboard Fixes
0:04:00Work through these in order in the RevenueCat dashboard. They cover every dashboard-side break in the chain.
A. One offering must be "Current"
getOfferings() returns the current offering in its current property. Go to Product Catalog → Offerings and confirm exactly one offering shows the Current badge. If not, open the offering's menu and select Make Current. Without it, offerings.current is nil even when everything else is perfect.
B. The current offering must have packages
Open the current offering. The Packages section must list at least one package (e.g. $rc_monthly, $rc_annual). An offering with zero packages returns an empty paywall with no error.
C. Every package needs a product for YOUR platform
Open each package and check the attached products. A package can hold an App Store product and a Google Play product, and it needs one for every platform you ship. A package with only an iOS product looks fine when you test on iOS and silently breaks on Android.
D. The ConfigurationError platform mismatch
This is the exact cause of the error below, and it's pure dashboard configuration:
PurchasesError(code=ConfigurationError, underlyingErrorMessage=You have configured
the SDK with a Play Store API key, but there are no Play Store products registered
in the RevenueCat dashboard for your offerings. …)
Translation: your app runs with a goog_ API key, but every product in your current offering's packages belongs to the App Store (or vice versa with an appl_ key and Play-only products). The store the SDK talks to has nothing to fetch. Fix:
- In Product Catalog → Products, click + New and create the product for the missing store (the Google Play product ID, e.g.
premium_monthly:monthly-autorenewfor subscriptions with base plans, or import it automatically). - Open your current offering → each package → Edit → attach the new store's product next to the existing one.
- Re-run the app. The error disappears once every package has a product for the active store.
E. Product IDs must match the store character-for-character
In Product Catalog → Products, compare each identifier against App Store Connect / Google Play Console. The match is case-sensitive and whitespace-sensitive: com.app.monthly ≠ com.app.Monthly, and a trailing space (easy to pick up when pasting) breaks it silently. When in doubt, delete the ID and re-paste it carefully.
F. The right API key, from the right project
- Use the public per-platform key from Project Settings → API Keys:
appl_…for App Store,goog_…for Play. Never the secretsk_…key in an app. - If you have multiple RevenueCat projects (e.g. staging/production), confirm the key belongs to the project whose dashboard you are editing. A key from the wrong project fetches that project's (empty) offerings, a classic "but I configured everything!" trap.
- Bundle ID (iOS) / package name (Android) in Apps & providers must match what your build actually uses, including
.debug/ flavor suffixes (e.g.com.app.dev). A suffixed debug build won't match an app configured ascom.app.
iOS / App Store Fixes
0:05:00Ordered by how often each one turns out to be the real cause, based on resolved community threads.
1. The Paid Applications Agreement (the #1 hidden cause)
Until this agreement is Active, Apple serves zero in-app purchase products to your app, including in sandbox. It also silently expires, which is how apps that "worked for months" suddenly show empty paywalls. Verified fix from multiple community threads:
- Go to App Store Connect → Business (formerly Agreements, Tax, and Banking).
- The Paid Applications Agreement must show Active: not "Pending", not "New", not expired. Accept the latest terms if prompted.
- Complete Banking Information and Tax Forms (W-9 for US, W-8BEN for non-US). The agreement isn't Active until both are done.
- Allow up to 24–48 hours for Apple's verification after completing it.
2. Product status: complete the metadata
In App Store Connect → your app → Subscriptions / In-App Purchases, each product must not be in Missing Metadata or Developer Action Needed. To get a product to a fetchable state it needs: a price for at least one region, at least one localization (display name + description), and a review screenshot. Ready to Submit is fine for sandbox/StoreKit testing; Approved is required for production.
3. Subscriptions must live in a Subscription Group
Apple requires every auto-renewable subscription to belong to a subscription group. If you created products via the API or in a hurry, confirm each one is assigned to a group. Ungrouped subscriptions don't resolve. (This was the verified fix in a widely-shared community write-up.)
4. Upload a binary once (StoreKit product activation)
The trap that catches brand-new apps: everything is configured correctly, the RevenueCat REST API returns your offerings, but availablePackages is 0 on device. Apple needs to process at least one uploaded binary to link new product IDs to your app. Archive and upload any build to App Store Connect (TestFlight is enough, no review needed), wait for processing (15 minutes to a few hours), and the products start resolving.
5. Bundle ID and capability
- Xcode → target → Signing & Capabilities: the Bundle Identifier must match App Store Connect and RevenueCat → Apps & providers exactly.
- Add the In-App Purchase capability to the target if it's missing.
6. Simulator needs a StoreKit Configuration file
The iOS Simulator does not talk to App Store Connect. Without a StoreKit Configuration file selected in your scheme, product requests return empty. This is the most common first-day experience. Either:
- File → New → File → StoreKit Configuration File, add your products with the exact same product IDs, then Product → Scheme → Edit Scheme → Run → Options → StoreKit Configuration → select the file; or
- Test on a physical device with a sandbox tester (Settings → App Store → Sandbox Account), keeping StoreKit Configuration set to None.
7. Country / region availability
A verified community fix: subscriptions were only available in one country, so testers elsewhere got empty offerings. In App Store Connect, check each product's Availability and make sure it covers every country you (and your testers) are in. Remember your sandbox account also has a country.
8. Propagation time
New products and metadata edits can take from a couple of hours up to ~24 hours to propagate through Apple's systems. If you created the products minutes ago and every checklist item above passes, wait before changing anything else.
Android / Google Play Fixes
0:05:00Unlike iOS, Android has no offline StoreKit equivalent: the Billing Library always talks to the live Google Play backend, so Play must know about your app, your products, and your tester. Ordered by frequency:
1. The app must be published to a testing track
Google Play returns no products for an app it has never seen. Upload a signed release build (AAB) to at least the Internal testing track (Play Console → Release → Testing → Internal testing) and make sure the track shows as available to testers. No review is required for internal testing.
2. The tester must be added AND opted in
This is the step everyone misses. Adding the Google account to the tester list is not enough:
- Add your Google account as a tester on the track.
- Open the opt-in link (Play Console shows it under "How testers join your test") on the device, signed in with that account, and tap Become a tester.
- Install the app at least once through Google Play via that link. After that, locally-built debug builds with the same
applicationIdand signature setup can fetch products too.
3. The build on the device must match Play's expectations
applicationIdinbuild.gradlemust match Play Console and RevenueCat → Apps & providers exactly. Watch out for.debugsuffixes added by build types.- The device's primary Google Play account must be the tester account. Multiple-account devices often query Play with the wrong one; if in doubt, clear Play Store data or use a profile with only the tester account.
- Emulators must include the Google Play Store image (not just "Google APIs"), with the tester signed in.
4. Products must be Active (and one-time products activated)
In Play Console → Monetize → Products, every subscription and in-app product referenced by your offering must show Active. Newly created in-app products start as drafts until you press Activate. For subscriptions, check the base plan is active too, since a subscription with no active base plan returns ITEM_UNAVAILABLE.
5. Service credentials: valid and propagated (up to 36 hours)
RevenueCat needs valid Play service credentials to validate purchases and (for imports) read your products. In RevenueCat → Apps & providers → Google Play, the credentials check must pass. Two verified gotchas:
- Freshly-created service credentials can take up to 36 hours to propagate through Google's systems. If you finished the Play setup today and everything else checks out, this is likely your answer. Wait it out.
- A quick way to force propagation along: edit any product's description in Play Console and save, which nudges Google's caches.
6. Country availability
Check the app's country availability (Play Console → Release → Production → Countries/regions) and each subscription's regional pricing. A tester in a country where the app or product isn't available gets empty results.
7. Still ITEM_UNAVAILABLE?
That response code means "Play knows the app but won't sell this SKU to this user". Re-walk causes 1–3 with this lens: right track? right account? installed from Play at least once? It almost always lands on one of those three.
React Native, Flutter & Cross-Platform
0:03:00Everything in steps 03–05 applies to React Native, Flutter, and KMP too. The SDK ultimately calls the same native StoreKit / Play Billing APIs. These causes are specific to cross-platform setups:
Expo Go cannot fetch products, ever
react-native-purchases is a native module, and Expo Go does not include it. Offerings/products will always come back empty there. Create a development build with EAS Build (npx expo run:ios / eas build --profile development) and test in that. The same applies to any environment that strips native modules.
Configure before you fetch (race conditions)
Calling getOfferings() before Purchases.configure() has completed (easy to do across JS module boundaries or in parallel useEffects) returns errors or empty results. Configure once, as early as possible (e.g. at app root), and await it where the API allows before fetching offerings.
Use the platform-correct key on each platform
Cross-platform apps need both keys, selected at runtime:
// React Native
import { Platform } from 'react-native';
import Purchases, { LOG_LEVEL } from 'react-native-purchases';
Purchases.setLogLevel(LOG_LEVEL.DEBUG);
Purchases.configure({
apiKey: Platform.OS === 'ios' ? 'appl_YOUR_IOS_KEY' : 'goog_YOUR_ANDROID_KEY',
});
Passing the iOS key on Android (or vice versa) produces exactly the ConfigurationError covered in step 03.
The getProducts() vs getOfferings() diagnostic
A pattern reported across React Native threads: Purchases.getProducts([...]) returns your products, but getOfferings() is empty. This is actually good news. It proves the store side works, and isolates the break to the dashboard chain (packages → offering → current). Go back to step 03 and check causes A–D; on iOS also confirm the Paid Applications Agreement, which can break offerings while getProducts still partially works in some setups.
Purchases.getOfferings() / Purchases.getProducts() from purchases_flutter, and the same diagnostic applies. Hot reload does not re-run configure(). Do a full restart after changing keys.
Debug with Code
0:04:00These snippets print exactly where the chain breaks: offerings object → current offering → packages → products. Run one, read the output, and it points you at the right section of this guide:
// Swift: walk the chain
Purchases.logLevel = .debug // before configure
Purchases.shared.getOfferings { offerings, error in
if let error = error {
print("❌ Offerings error: \(error.localizedDescription)")
// ConfigurationError → step 03 (platform mismatch / dashboard)
// Network errors → step 08
return
}
guard let offerings = offerings else {
print("❌ Offerings object is nil → check API key & project (step 03-F)")
return
}
print("All offerings: \(offerings.all.keys.joined(separator: ", "))")
guard let current = offerings.current else {
print("⚠️ No current offering → mark one Current (step 03-A)")
return
}
print("Current offering: \(current.identifier)")
if current.availablePackages.isEmpty {
print("⚠️ 0 packages resolved → store could not serve the products (steps 04-05)")
print(" (If the dashboard shows packages, this is store-side: agreement,")
print(" product status, binary activation, testing track, or tester setup.)")
} else {
for package in current.availablePackages {
print("✅ \(package.identifier) → \(package.storeProduct.productIdentifier) @ \(package.storeProduct.localizedPriceString)")
}
}
}
// Kotlin: walk the chain
Purchases.logLevel = LogLevel.DEBUG // before configure
Purchases.sharedInstance.getOfferingsWith(
onError = { error ->
Log.e("RC", "❌ Offerings error: ${error.message} (code: ${error.code})")
// ConfigurationError → step 03 (platform mismatch / dashboard)
},
onSuccess = { offerings ->
Log.d("RC", "All offerings: ${offerings.all.keys.joinToString()}")
val current = offerings.current
if (current == null) {
Log.w("RC", "⚠️ No current offering → mark one Current (step 03-A)")
return@getOfferingsWith
}
Log.d("RC", "Current offering: ${current.identifier}")
if (current.availablePackages.isEmpty()) {
Log.w("RC", "⚠️ 0 packages resolved → Play could not serve the products (step 05)")
Log.w("RC", " Check: testing track, tester opt-in, install-from-Play, Active products")
} else {
current.availablePackages.forEach { pkg ->
Log.d("RC", "✅ ${pkg.identifier} → ${pkg.product.id} @ ${pkg.product.price.formatted}")
}
}
}
)
// React Native: walk the chain (same logic for Flutter's purchases_flutter)
import Purchases, { LOG_LEVEL } from 'react-native-purchases';
Purchases.setLogLevel(LOG_LEVEL.DEBUG);
try {
const offerings = await Purchases.getOfferings();
console.log('All offerings:', Object.keys(offerings.all));
if (!offerings.current) {
console.warn('⚠️ No current offering → mark one Current (step 03-A)');
} else if (offerings.current.availablePackages.length === 0) {
console.warn('⚠️ 0 packages resolved → store-side issue (steps 04-05)');
} else {
offerings.current.availablePackages.forEach((pkg) =>
console.log(`✅ ${pkg.identifier} → ${pkg.product.identifier} @ ${pkg.product.priceString}`),
);
}
} catch (e) {
console.error('❌ Offerings error:', e);
// ConfigurationError → step 03; native module missing in Expo Go → step 06
}
Reading the debug log
Configuring Purchases with API key: appl_/goog_…: confirm it's the key and platform you expect.Requesting products from the store with identifiers: …: the dashboard chain works; the question is now what the store returns.Fetched 0 products from the store/ invalid identifiers listed: store-side cause, go to step 04 (iOS) or step 05 (Android).- No product request logged at all: dashboard-side cause, go to step 03.
Still Stuck? + Related Guides
0:02:00Isolation tools: prove which layer is broken
- Wait out propagation: new products (up to ~24h on Apple), new Play credentials (up to 36h), and fresh agreement signatures all take time. If you set things up today, the most effective fix is often tomorrow.
- Network family errors (
NetworkError, timeouts): check connectivity, disable VPN/proxy, confirmapi.revenuecat.comisn't firewalled, and add retry-with-backoff for paywall loads. During App Store review specifically, design the paywall to degrade gracefully. - Test the dashboard chain without your app: call the REST API. If this returns your offering with products, the dashboard is fine and the issue is store-side or in your app:
bash
curl -s 'https://api.revenuecat.com/v1/subscribers/test_user/offerings' \ -H 'Authorization: Bearer YOUR_PUBLIC_API_KEY' \ -H 'X-Platform: ios' # or android - Test your config without your code: RevenueCat's SampleCat sample app with your API key. If SampleCat fetches offerings and your app doesn't, the difference is in your app (initialization order, key, StoreKit config, bundle ID).
- Test your code without the stores: RevenueCat's Test Store (iOS) / Test Store (Android) bypasses Apple/Google entirely. If offerings load with a Test Store key, your code and dashboard are fine, so the issue is store-side setup.
- Stale cache after dashboard edits: offerings are cached for ~5 minutes; force-quit and relaunch after dashboard changes (and on Android, the Play Store app's own cache can lag; clearing Play Store data helps).
The complete checklist
- ☐ One offering marked Current, with packages, each holding a product for your platform
- ☐ Product IDs identical to the store (case, whitespace)
- ☐ Correct public API key (
appl_/goog_) from the correct project; bundle ID / applicationId matches - ☐ iOS: Paid Applications Agreement Active (banking + tax complete, not expired)
- ☐ iOS: products have price, localization, review screenshot; subscriptions in a subscription group
- ☐ iOS: at least one binary uploaded & processed; Simulator uses a StoreKit Configuration file (or device + sandbox account with it set to None)
- ☐ Android: signed build on internal testing; tester added and opted in; installed once via Play
- ☐ Android: products (and base plans) Active; Play credentials valid and > 36h old
- ☐ Both: product/app availability covers your country; debug logs read end-to-end
If every box is ticked and offerings are still empty, gather your debug log and ask in the RevenueCat Community or contact support, and include the log lines around Error fetching offerings.
Related guides
- Troubleshooting: Fetching Offerings: issue-by-issue deep dive with more error message variants
- Troubleshooting: App Store Issues: iOS product approval and StoreKit specifics
- Troubleshooting: Google Play Issues: Android billing and credentials specifics
- iOS Codelab / Android Codelab: full integration walkthroughs
- Official docs: Troubleshooting Offerings