Skip to main content
Version: 3.1 (Current)

Expo IAP

Expo IAP is a powerful in-app purchase solution for Expo and React Native applications that conforms to the Open IAP specification. It provides a unified API for handling in-app purchases across iOS and Android platforms with comprehensive error handling and modern TypeScript support.

If you're shipping an app with expo-iap, we’d love to hear about it—please share your product and feedback in Who’s using Expo IAP?. Community stories help us keep improving the ecosystem.

Sponsors & Community Support

We're building the OpenIAP ecosystem—defining the spec at openiap.dev, maintaining OpenIAP for the shared type system, and shipping native SDKs such as openiap-apple and openiap-google. These modules power expo-iap, flutter_inapp_purchase, kmp-iap, and react-native-iap. After simplifying fragmented APIs, the next milestone is a streamlined purchase flow: initConnection → fetchProducts → requestPurchase → (server receipt validation) → finishTransaction.

Your sponsorship keeps this work moving—ensuring more developers across platforms, OS, and frameworks can implement IAPs without headaches while we expand to additional plugins and payment systems. Sponsors receive shout-outs in each release and, depending on tier, can request tailored support. If you’re interested—or have rollout feedback to share—you can view sponsorship options at openiap.dev/sponsors.

📚 Guides

🚀 Quick Start

Installation

Install the package using your favorite package manager:

npm install expo-iap

1. Basic Setup

First, import and initialize the IAP hook:

import {useIAP} from 'expo-iap';

function MyStore() {
const {
connected,
products,
fetchProducts,
requestPurchase,
finishTransaction,
} = useIAP({
onPurchaseSuccess: async (purchase) => {
console.log('Purchase successful:', purchase);

// IMPORTANT: Verify receipt on your backend before finishing transaction
const isValid = await verifyReceiptOnServer(purchase);

if (isValid) {
await finishTransaction({purchase, isConsumable: true});
}
},
onPurchaseError: (error) => {
console.error('Purchase failed:', error);
},
});

const productIds = ['your.product.id', 'your.premium.subscription'];
}

2. Fetch Products

Load your products when the store connects:

useEffect(() => {
if (connected) {
// Fetch your products
fetchProducts({skus: productIds, type: 'in-app'});
}
}, [connected]);

3. Display Products

Show available products to users:

return (
<View>
<Text>Store Status: {connected ? 'Connected' : 'Connecting...'}</Text>

{products.map((product) => (
<View key={product.id} style={styles.productItem}>
<Text style={styles.productTitle}>{product.title}</Text>
<Text style={styles.productPrice}>{product.displayPrice}</Text>
<Button title="Buy Now" onPress={() => handlePurchase(product.id)} />
</View>
))}
</View>
);

4. Handle Purchases

Process purchase requests with our new platform-specific API (v2.7.0+):

const handlePurchase = async (productId: string) => {
try {
await requestPurchase({
request: {
ios: {
sku: productId,
},
android: {
skus: [productId],
},
},
});
} catch (error) {
console.error('Purchase failed:', error);
}
};

No more Platform.OS checks! The new API automatically handles platform differences. iOS can only purchase one product at a time, while Android supports purchasing multiple products in a single transaction.

5. Complete Transactions

Finish purchases in the success callback:

const {connected, products, fetchProducts, requestPurchase, finishTransaction} =
useIAP({
onPurchaseSuccess: async (purchase) => {
try {
console.log('Purchase completed:', purchase.id);

// IMPORTANT: Verify receipt on your backend before finishing transaction
const isValid = await verifyReceiptOnServer(purchase);

if (!isValid) {
console.error('Receipt validation failed');
return;
}

// Grant the purchase to user here
await grantPurchaseToUser(purchase);

// Finish the transaction
await finishTransaction({
purchase,
isConsumable: true, // Set based on your product type
});
} catch (error) {
console.error('Failed to complete purchase:', error);
}
},
onPurchaseError: (error) => {
console.error('Purchase failed:', error);
},
});

Complete Basic Example

Here's a complete working example:

import React, {useEffect} from 'react';
import {View, Text, Button, StyleSheet} from 'react-native';
import {useIAP} from 'expo-iap';

export default function SimpleStore() {
const {
connected,
products,
fetchProducts,
requestPurchase,
finishTransaction,
} = useIAP({
onPurchaseSuccess: async (purchase) => {
try {
console.log('Purchase completed:', purchase.id);

// IMPORTANT: Verify receipt on your backend before finishing transaction
const isValid = await verifyReceiptOnServer(purchase);

if (!isValid) {
console.error('Receipt validation failed');
return;
}

// Grant purchase to user
await grantPurchaseToUser(purchase);

// Finish the transaction
await finishTransaction({
purchase,
isConsumable: true,
});
} catch (error) {
console.error('Failed to complete purchase:', error);
}
},
onPurchaseError: (error) => {
console.error('Purchase failed:', error);
},
});

const productIds = ['com.example.coins.pack1', 'com.example.premium'];

useEffect(() => {
if (connected) {
fetchProducts({skus: productIds, type: 'in-app'});
}
}, [connected]);

const handlePurchase = async (productId: string) => {
try {
await requestPurchase({
request: {
ios: {
sku: productId,
},
android: {
skus: [productId],
},
},
});
} catch (error) {
console.error('Purchase failed:', error);
}
};

return (
<View style={styles.container}>
<Text style={styles.status}>
Store: {connected ? 'Connected ✅' : 'Connecting...'}
</Text>

{products.map((product) => (
<View key={product.id} style={styles.product}>
<Text style={styles.title}>{product.title}</Text>
<Text style={styles.price}>{product.displayPrice}</Text>
<Button title="Buy Now" onPress={() => handlePurchase(product.id)} />
</View>
))}
</View>
);
}

const styles = StyleSheet.create({
container: {padding: 20},
status: {fontSize: 16, marginBottom: 20},
product: {
padding: 15,
marginVertical: 5,
backgroundColor: '#f0f0f0',
borderRadius: 8,
},
title: {fontSize: 16, fontWeight: 'bold'},
price: {fontSize: 14, color: '#666', marginVertical: 5},
});

🏗️ Architecture

Expo IAP is built with a modern architecture that emphasizes:

  • Type Safety: Comprehensive TypeScript definitions for all APIs
  • Error Resilience: Centralized error handling with meaningful error codes
  • Platform Abstraction: Unified API that handles platform differences internally
  • Performance: Optimized for minimal bundle size and runtime performance

📱 Platform Support

PlatformSupportNotes
iOSStoreKit 1 & 2 (StoreKit 2 requires iOS 15+)
AndroidGoogle Play Billing v5+
Expo Go⚠️Limited (requires custom development client)
Expo Dev ClientFull support
Bare React NativeFull support

🎯 What's Next?

📦 Setup & Configuration

🔧 Implementation

📚 Guides

🛠️ Advanced Topics

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

📝 License

This project is licensed under the MIT License - see the LICENSE file for details.