By the end of this lesson, you will:
Freemium is the dominant mobile app monetization model. Spotify, Duolingo, and Tinder offer core features free while charging for premium perks. Done right, freemium creates millions of happy free users and thousands of paying customers.
The key is value exchange: free users get genuine utility, paid users get transformative upgrades. When users see the value, they convert willingly and recommend your app. Poor freemium feels like a bait-and-switch. In this lesson, we'll design freemium experiences users love.
Different approaches to free vs. paid:
One. Feature-Limited
Example: Evernote
2. Usage-Limited
Example: Grammarly
3. Capacity-Limited
Example: Dropbox
4. Time-Limited
Example: YouTube Premium
5. Hybrid
Example: Spotify
Understanding user decision-making:
const FreemiumPsychology = {
// Loss Aversion: Users fear losing what they have
showWhatTheyMiss: () => {
// "You've used 5/5 exports this month"
// "Unlock unlimited exports"
},
// Social Proof: Others are upgrading
displayPremiumUsers: () => {
// "Join 50,000+ premium members"
// "Trusted by professionals worldwide"
},
// Scarcity: Limited-time offers
createUrgency: () => {
// "50% off - 3 days left"
// "Cyber Monday: $2.99/month (was $9.99)"
},
// Anchoring: Make price seem small
compareValue: () => {
// "Less than a coffee per month"
// "$0.33/day for unlimited access"
},
// Reciprocity: Give value first
freeTrialStrategy: () => {
// Let users experience premium
// They'll want to keep it
},
// Progress: Show how far they've come
highlightInvestment: () => {
// "You've completed 45 workouts"
// "Unlock advanced analytics"
},
};
Deciding what's free vs. paid:
export const FeatureStrategy = {
free: {
// Must provide real value
core: [
'Basic functionality',
'Essential features',
'Value proposition demo',
],
// Examples for fitness app
fitnessApp: [
'Track workouts',
'View today stats',
'Basic goals',
'7-day history',
],
},
premium: {
// Enhance, don't enable
advanced: [
'Power user features',
'Professional tools',
'Convenience upgrades',
'Exclusive content',
],
// Examples for fitness app
fitnessApp: [
'Unlimited history',
'Custom workout plans',
'Advanced analytics',
'Export data',
'No ads',
'Priority support',
],
},
// The "Power User Test"
shouldBePremium: (feature) => {
// Ask these questions:
const questions = [
'Would power users pay for this?',
'Does this save significant time?',
'Is this a professional/business need?',
'Does this require expensive resources?',
'Is this cosmetic/convenience?',
];
// If 2+ are "yes", consider premium
},
};
Guide users from free to paid:
export const ConversionFunnel = {
// Stage 1: Onboarding (Day 0-3)
onboarding: {
goal: 'Hook users with core value',
strategy: 'Show premium teasers subtly',
example: 'Badge showing "Premium" features',
},
// Stage 2: Engagement (Day 4-14)
engagement: {
goal: 'Build habit, show limitations',
strategy: 'Soft paywalls at natural boundaries',
example: '"Export workout data" locked after 3 exports',
},
// Stage 3: Conversion Moment (Day 15-30)
conversion: {
goal: 'Trigger upgrade decision',
strategy: 'Strong CTA when user hits limit',
example: 'Modal: "You\'ve logged 15 workouts! Unlock unlimited history"',
},
// Stage 4: Retention (Day 30+)
retention: {
goal: 'Keep subscribers happy',
strategy: 'Exclusive features, regular updates',
example: 'Monthly premium-only content',
},
};
Display upgrade screens at strategic moments:
import { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Modal } from 'react-native';
export default function PaywallModal({ visible, feature, onClose, onUpgrade }) {
return (
<Modal
visible={visible}
transparent={true}
animationType="slide"
onRequestClose={onClose}
>
<View style={styles.overlay}>
<View style={styles.modal}>
<Text style={styles.icon}>🔒</Text>
<Text style={styles.title}>Unlock {feature.name}</Text>
<Text style={styles.description}>{feature.description}</Text>
<View style={styles.benefits}>
<Text style={styles.benefitsTitle}>Premium Benefits:</Text>
{feature.benefits.map((benefit, index) => (
<View key={index} style={styles.benefit}>
<Text style={styles.checkmark}>✓</Text>
<Text style={styles.benefitText}>{benefit}</Text>
</View>
))}
</View>
<TouchableOpacity style={styles.upgradeButton} onPress={onUpgrade}>
<Text style={styles.upgradeButtonText}>
Upgrade to Premium - $9.99/mo
</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.closeButton} onPress={onClose}>
<Text style={styles.closeButtonText}>Maybe Later</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
);
}
const styles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.7)',
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
modal: {
backgroundColor: '#fff',
borderRadius: 24,
padding: 30,
width: '100%',
maxWidth: 400,
alignItems: 'center',
},
icon: {
fontSize: 64,
marginBottom: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 12,
textAlign: 'center',
},
description: {
fontSize: 16,
color: '#666',
textAlign: 'center',
marginBottom: 24,
},
benefits: {
width: '100%',
marginBottom: 24,
},
benefitsTitle: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 12,
},
benefit: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 8,
},
checkmark: {
fontSize: 18,
color: '#4CAF50',
marginRight: 8,
},
benefitText: {
fontSize: 14,
color: '#333',
},
upgradeButton: {
backgroundColor: '#007AFF',
borderRadius: 12,
padding: 16,
width: '100%',
alignItems: 'center',
marginBottom: 12,
},
upgradeButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
},
closeButton: {
padding: 12,
},
closeButtonText: {
color: '#666',
fontSize: 14,
},
});
Track feature usage and prompt upgrades:
import AsyncStorage from '@react-native-async-storage/async-storage';
const USAGE_KEY_PREFIX = '@usage_';
export class UsageLimiter {
constructor(featureName, freeLimit) {
this.featureName = featureName;
this.freeLimit = freeLimit;
this.storageKey = `${USAGE_KEY_PREFIX}${featureName}`;
}
async getUsage() {
try {
const data = await AsyncStorage.getItem(this.storageKey);
if (!data) return { count: 0, resetDate: this.getNextResetDate() };
const usage = JSON.parse(data);
const now = new Date();
const resetDate = new Date(usage.resetDate);
// Reset if past reset date
if (now >= resetDate) {
return { count: 0, resetDate: this.getNextResetDate() };
}
return usage;
} catch (error) {
return { count: 0, resetDate: this.getNextResetDate() };
}
}
async incrementUsage() {
const usage = await this.getUsage();
usage.count += 1;
await AsyncStorage.setItem(this.storageKey, JSON.stringify(usage));
return usage;
}
async canUseFeature(isPremium) {
if (isPremium) return { allowed: true, remaining: Infinity };
const usage = await this.getUsage();
const allowed = usage.count < this.freeLimit;
const remaining = Math.max(0, this.freeLimit - usage.count);
return { allowed, remaining, usage };
}
getNextResetDate() {
// Reset monthly
const now = new Date();
const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1);
return nextMonth.toISOString();
}
}
// Usage example
const exportLimiter = new UsageLimiter('export_workout', 3);
export const handleExportWorkout = async (isPremium, showPaywall) => {
const { allowed, remaining } = await exportLimiter.canUseFeature(isPremium);
if (!allowed) {
showPaywall({
name: 'Unlimited Exports',
description: 'You\'ve used all 3 exports this month',
benefits: [
'Unlimited workout exports',
'CSV and PDF formats',
'Priority support',
],
});
return false;
}
// Proceed with export
await exportLimiter.incrementUsage();
// Warn when approaching limit
if (remaining === 1 && !isPremium) {
Alert.alert(
'Last Free Export',
'You have 1 export remaining this month. Upgrade for unlimited exports!'
);
}
return true;
};
Mark premium features in UI:
import { View, Text, StyleSheet } from 'react-native';
export function PremiumBadge({ size = 'small' }) {
return (
<View style={[styles.badge, size === 'large' && styles.badgeLarge]}>
<Text style={[styles.badgeText, size === 'large' && styles.badgeTextLarge]}>
⭐ PRO
</Text>
</View>
);
}
const styles = StyleSheet.create({
badge: {
backgroundColor: '#FFD700',
borderRadius: 6,
paddingHorizontal: 8,
paddingVertical: 2,
},
badgeLarge: {
paddingHorizontal: 12,
paddingVertical: 4,
},
badgeText: {
fontSize: 10,
fontWeight: 'bold',
color: '#000',
},
badgeTextLarge: {
fontSize: 12,
},
});
Track and optimize monetization:
export const MonetizationMetrics = {
// Key metrics to track
metrics: {
conversionRate: {
formula: '(paid users / total users) * 100',
target: '2-5%', // Industry average
},
ARPU: {
formula: 'total revenue / total users',
description: 'Average Revenue Per User',
},
ARPPU: {
formula: 'total revenue / paying users',
description: 'Average Revenue Per Paying User',
},
timeToConversion: {
formula: 'days from install to first purchase',
target: '7-30 days',
},
churnRate: {
formula: '(canceled subs / total subs) * 100',
target: '<5% monthly',
},
},
// Track events
trackPaywallView: (featureName) => {
// Analytics.track('Paywall Viewed', { feature: featureName });
},
trackUpgradeClick: (plan) => {
// Analytics.track('Upgrade Clicked', { plan });
},
trackPurchaseComplete: (plan, price) => {
// Analytics.track('Purchase Completed', { plan, price });
},
trackPaywallDismiss: (featureName) => {
// Analytics.track('Paywall Dismissed', { feature: featureName });
},
};
Complete freemium fitness app structure:
import { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, ScrollView } from 'react-native';
export default function FreemiumFitnessApp() {
const [isPremium, setIsPremium] = useState(false);
const [showPaywall, setShowPaywall] = useState(false);
const [paywallFeature, setPaywallFeature] = useState(null);
const features = [
{
name: 'Track Workouts',
icon: '✅',
available: 'free',
},
{
name: 'View 7 Days History',
icon: '📊',
available: 'free',
},
{
name: 'Basic Goals',
icon: '🎯',
available: 'free',
},
{
name: 'Unlimited History',
icon: '📈',
available: 'premium',
benefit: 'View all-time workout history',
},
{
name: 'Custom Plans',
icon: '📝',
available: 'premium',
benefit: 'Create personalized workout plans',
},
{
name: 'Advanced Analytics',
icon: '📉',
available: 'premium',
benefit: 'Deep insights and trends',
},
{
name: 'Export Data',
icon: '💾',
available: 'premium',
benefit: 'Export to CSV/PDF',
},
{
name: 'No Ads',
icon: '🚫',
available: 'premium',
benefit: 'Ad-free experience',
},
];
const handleFeatureTap = (feature) => {
if (feature.available === 'premium' && !isPremium) {
setPaywallFeature({
name: feature.name,
description: feature.benefit,
benefits: [
'All premium features',
'Unlimited usage',
'Priority support',
'Regular updates',
],
});
setShowPaywall(true);
} else {
// Open feature
console.log('Opening:', feature.name);
}
};
const handleUpgrade = () => {
// Navigate to purchase flow
setShowPaywall(false);
console.log('Navigate to purchase');
};
return (
<ScrollView style={styles.container}>
<Text style={styles.title}>Fitness Tracker</Text>
{!isPremium && (
<TouchableOpacity
style={styles.upgradePrompt}
onPress={() => handleFeatureTap(features[3])}
>
<Text style={styles.upgradePromptText}>
⭐ Upgrade to Premium - Unlock All Features
</Text>
</TouchableOpacity>
)}
<View style={styles.featuresGrid}>
{features.map((feature, index) => (
<TouchableOpacity
key={index}
style={[
styles.featureCard,
feature.available === 'premium' && !isPremium && styles.featureCardLocked,
]}
onPress={() => handleFeatureTap(feature)}
>
<Text style={styles.featureIcon}>{feature.icon}</Text>
<Text style={styles.featureName}>{feature.name}</Text>
{feature.available === 'premium' && (
<View style={styles.premiumBadge}>
<Text style={styles.premiumBadgeText}>PRO</Text>
</View>
)}
{feature.available === 'premium' && !isPremium && (
<View style={styles.lockOverlay}>
<Text style={styles.lockIcon}>🔒</Text>
</View>
)}
</TouchableOpacity>
))}
</View>
{showPaywall && paywallFeature && (
<PaywallModal
visible={showPaywall}
feature={paywallFeature}
onClose={() => setShowPaywall(false)}
onUpgrade={handleUpgrade}
/>
)}
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
padding: 20,
},
title: {
fontSize: 28,
fontWeight: 'bold',
marginBottom: 20,
},
upgradePrompt: {
backgroundColor: '#FFD700',
borderRadius: 12,
padding: 16,
marginBottom: 20,
alignItems: 'center',
},
upgradePromptText: {
fontSize: 16,
fontWeight: 'bold',
color: '#000',
},
featuresGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
},
featureCard: {
width: '48%',
backgroundColor: '#fff',
borderRadius: 12,
padding: 20,
marginBottom: 16,
alignItems: 'center',
position: 'relative',
},
featureCardLocked: {
opacity: 0.7,
},
featureIcon: {
fontSize: 40,
marginBottom: 12,
},
featureName: {
fontSize: 14,
textAlign: 'center',
fontWeight: '600',
},
premiumBadge: {
position: 'absolute',
top: 8,
right: 8,
backgroundColor: '#FFD700',
borderRadius: 6,
paddingHorizontal: 6,
paddingVertical: 2,
},
premiumBadgeText: {
fontSize: 10,
fontWeight: 'bold',
},
lockOverlay: {
position: 'absolute',
bottom: 8,
right: 8,
},
lockIcon: {
fontSize: 20,
},
});
| Pitfall | Solution |
|---|---|
| Free version too limited | Provide genuine value, free users should recommend app |
| Too many paywalls | Use soft paywalls sparingly, don't interrupt every action |
| Aggressive upselling | Be respectful, offer value not pressure |
| Unclear premium benefits | Show concrete benefits, not vague "pro features" |
| No trial period | Let users experience premium before buying |
In the next lesson, we'll explore Play Billing and IAP, implementing the technical integration for one-time purchases and managing the purchase flow.