RevenueCat ConfigurationError: SDK Not Properly Initialized

8:00

What 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

text
# 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

  1. Calling Purchases.shared before configure()

    Any call to Purchases.shared — including setting up a delegate, fetching offerings, or listening for customer info updates — before configure() has run will throw this error. This commonly happens when a SwiftUI view's init or .onAppear fires before the App struct's initializer completes.

  2. 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 cause configure() to reject the key. iOS keys start with appl_, Android keys with goog_, and cross-platform keys with rcb_.

  3. configure() called after the shared instance is accessed

    In 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 AppDelegate or the Application class runs. Review your startup order carefully.

  4. Missing configure() call in App delegate / Application class

    If 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)

swift
// 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)

swift
// 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
        }
    }
}
SwiftUI warning: Do not call 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)

kotlin
// 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:

xml
<!-- AndroidManifest.xml -->
<application
    android:name=".MyApplication"
    android:label="@string/app_name"
    ...>
    <!-- activities, services, etc. -->
</application>
Android requirement: You must use an Application class, not an Activity. Activities are created after the application lifecycle starts and may be recreated. The Application class onCreate() is guaranteed to run exactly once before any Activity is created.

Flutter — main.dart

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

javascript
// 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
Security note: RevenueCat Public API keys are safe to include in your app binary — they are designed to be public. Do not confuse them with the Secret API key (used for server-side calls only), which must never be included in client-side code.