RevenueCat ConfigurationError: SDK Not Properly Initialized
8:00What This Error Means
The ConfigurationError (sometimes called "Purchases not configured") is a fatal error thrown by the RevenueCat SDK when your code accesses the Purchases.shared singleton before calling Purchases.configure(). Because RevenueCat requires an API key and optional settings to be supplied at startup, accessing the shared instance before configuration is an invalid operation and causes an immediate crash or exception.
The fix is always the same: call Purchases.configure() as early as possible in your app lifecycle, before any view or component attempts to access Purchases.shared.
Error Messages
# iOS (Swift)
Fatal error: Purchases not configured.
Call Purchases.configure() before calling Purchases.shared
# Android (Kotlin)
UninitializedPropertyAccessException: PurchasesConfiguration must be
configured before calling Purchases.sharedInstance
# Flutter
PlatformException(purchases_not_configured,
Purchases must be configured before calling any methods, null)
# React Native
Error: Purchases not configured.
Make sure to call Purchases.configure() before calling any other methods.
Common Causes
-
Calling
Purchases.sharedbeforeconfigure()Any call to
Purchases.shared— including setting up a delegate, fetching offerings, or listening for customer info updates — beforeconfigure()has run will throw this error. This commonly happens when a SwiftUI view'sinitor.onAppearfires before the App struct's initializer completes. -
API key is empty, wrong, or for the wrong platform
Passing an empty string
""as the API key, or accidentally using an iOS key in an Android project (or vice versa), will causeconfigure()to reject the key. iOS keys start withappl_, Android keys withgoog_, and cross-platform keys withrcb_. -
configure()called after the shared instance is accessedIn some cases, especially with dependency injection containers or lazy initialization, code may attempt to resolve a RevenueCat-dependent service before the SDK's configuration code in
AppDelegateor the Application class runs. Review your startup order carefully. -
Missing
configure()call in App delegate / Application classIf
configure()was accidentally deleted, commented out, or placed in a code path that doesn't always execute (e.g., behind a feature flag or a conditional), the shared instance will never be ready. Always configure unconditionally at app startup.
Platform-Specific Initialization Code
The following are the correct, canonical ways to initialize the RevenueCat SDK on each platform. Use these as a reference against your own implementation.
Swift — UIKit (AppDelegate)
// AppDelegate.swift
import UIKit
import RevenueCat
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Configure RevenueCat as the FIRST thing in app startup
Purchases.logLevel = .debug // Remove in production
Purchases.configure(withAPIKey: "appl_YOUR_REVENUECAT_IOS_KEY")
// Optional: set app user ID if you manage your own user IDs
// Purchases.configure(withAPIKey: "appl_YOUR_KEY", appUserID: "user_123")
return true
}
}
Swift — SwiftUI (@main App struct)
// MyApp.swift
import SwiftUI
import RevenueCat
@main
struct MyApp: App {
init() {
// Configure in the App initializer — runs before any view body
Purchases.logLevel = .debug // Remove in production
Purchases.configure(withAPIKey: "appl_YOUR_REVENUECAT_IOS_KEY")
}
var body: some Scene {
WindowGroup {
ContentView()
// By the time ContentView appears, Purchases.shared is ready
}
}
}
Purchases.configure() inside a View.onAppear modifier. Views can appear and disappear multiple times, which would result in repeated reconfiguration. Always configure in App.init() or AppDelegate.
Kotlin — Application Class (Android)
// MyApplication.kt
import android.app.Application
import com.revenuecat.purchases.LogLevel
import com.revenuecat.purchases.Purchases
import com.revenuecat.purchases.PurchasesConfiguration
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Configure RevenueCat in Application.onCreate() — NOT in an Activity
Purchases.logLevel = LogLevel.DEBUG // Remove in production
Purchases.configure(
PurchasesConfiguration.Builder(this, "goog_YOUR_REVENUECAT_ANDROID_KEY")
// .appUserID("user_123") // Optional: set your own user ID
.build()
)
}
}
Then register the Application class in AndroidManifest.xml:
<!-- AndroidManifest.xml -->
<application
android:name=".MyApplication"
android:label="@string/app_name"
...>
<!-- activities, services, etc. -->
</application>
onCreate() is guaranteed to run exactly once before any Activity is created.
Flutter — main.dart
// main.dart
import 'package:flutter/material.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
import 'dart:io' show Platform;
void main() async {
// Ensure Flutter bindings are initialized before calling platform channels
WidgetsFlutterBinding.ensureInitialized();
// Configure RevenueCat before runApp()
await Purchases.setLogLevel(LogLevel.debug); // Remove in production
PurchasesConfiguration configuration;
if (Platform.isIOS) {
configuration = PurchasesConfiguration("appl_YOUR_REVENUECAT_IOS_KEY");
} else if (Platform.isAndroid) {
configuration = PurchasesConfiguration("goog_YOUR_REVENUECAT_ANDROID_KEY");
} else {
throw UnsupportedError("Unsupported platform");
}
await Purchases.configure(configuration);
runApp(const MyApp());
}
React Native — App.js
// App.js (or App.tsx)
import React, { useEffect } from 'react';
import { Platform } from 'react-native';
import Purchases, { LOG_LEVEL } from 'react-native-purchases';
const APIKeys = {
apple: 'appl_YOUR_REVENUECAT_IOS_KEY',
google: 'goog_YOUR_REVENUECAT_ANDROID_KEY',
};
export default function App() {
useEffect(() => {
// Configure RevenueCat as early as possible in the root component
const configure = async () => {
Purchases.setLogLevel(LOG_LEVEL.DEBUG); // Remove in production
if (Platform.OS === 'ios') {
await Purchases.configure({ apiKey: APIKeys.apple });
} else if (Platform.OS === 'android') {
await Purchases.configure({ apiKey: APIKeys.google });
}
};
configure().catch(console.error);
}, []); // Empty dependency array — runs once on mount
return (
// ... your app UI
);
}
API Key Reference
Use this table to confirm you're using the correct key format for each platform:
| Platform | Key prefix | Where to find it |
|---|---|---|
| iOS (native) | appl_ |
RevenueCat dashboard → Project Settings → Apps → iOS app → Public API key |
| Android (native) | goog_ |
RevenueCat dashboard → Project Settings → Apps → Android app → Public API key |
| Flutter / React Native / Capacitor | appl_ or goog_ |
Use the platform-specific key for each platform inside conditional logic |
Related Guides
- iOS In-App Purchases Tutorial — Full iOS SDK integration walkthrough including configuration
- Android In-App Billing Tutorial — Full Android SDK integration walkthrough including configuration
- RevenueCat Troubleshooting Guide — Comprehensive troubleshooting reference for all RevenueCat errors
- SDK Debug Messages — How to read and interpret RevenueCat debug log output