Looking to build a mobile app with Medusa.js? This comprehensive guide walks you through creating a React Native Medusa mobile app from scratch. Whether you’re starting a new project or looking for a React Native starter Medusa template, you’ll learn how to build a production-ready mobile e-commerce solution.

What is Medusa?
Medusa is an open-source headless commerce engine that enables developers to create unique and scalable e-commerce experiences. Unlike traditional e-commerce platforms, Medusa offers:
- Headless Architecture: Complete freedom in frontend implementation
- Extensible Core: Plugin system for custom functionality
- Developer-First: Built with TypeScript and modern development practices
- Multi-Region Support: Built-in multi-currency support based on region
- Order Management: Comprehensive order lifecycle handling
- Inventory Management: Built-in inventory and variant management
- Modular architecture
Why React Native?
React Native is a framework for building native mobile applications using React:
- Native Performance: Direct access to native platform capabilities
- Code Reusability: Share logic between iOS and Android
- Large Ecosystem: Extensive library of third-party packages
- Hot Reload: Fast development with instant feedback
- Strong Community: Active development and community support
Using React Native with Medusa
Combining React Native and Medusa.js creates powerful mobile e-commerce possibilities:
- Native Mobile Experience: Build a React Native Medusa app with native UI components
- Mobile-First Features: Leverage device capabilities like push notifications
- Offline Support: Implement AsyncStorage for cart and user data
- Performance Optimization: Native rendering for smooth product browsing
- Cross-platform support: Build an Android and iOS e-commerce mobile app using a single code base
Introducing Medusa Mobile
Medusa Mobile is a complete React Native Medusa starter. It is not just a starter, it comes with 90% of the functionality that you need for a production-ready app.
Features of Medusa Mobile:
- 🛍️ Product browsing with infinite scroll
- 👤 User authentication and profile management
- 🔍 Categories and collections
- 🛒 Cart management
- 🏃♂️ Guest checkout
- 📦 Order tracking
- 🎨 Multiple themes with dark mode

Setting up Medusa Backend
Before running the mobile app, you need a Medusa backend. Set it up in a few steps by following the official docs – Install Medusa backend application
Setting up Medusa Mobile
Now let’s set up the React Native Medusa mobile app
📋 Prerequisites
Before you begin, ensure you have:
- Node.js (v20 or newer)
- React Native development environment – Set Up Your Environment
- A running Medusa v2 backend server – Medusa v2 installation
Step 1: Environment Setup
- Clone the repository:
git clone [email protected]:bloomsynth/medusa-mobile-react-native.git medusa-mobile
cd medusa-mobile
- Install dependencies:
npm install
- Configure environment variables:
cp .env.template .env
Edit .env
with your Medusa backend URL and publishable API key.
NOTE: Update the MEDUSA_BACKEND_URL
in your .env
file. If you set the URL as localhost, then the Android emulator will not be able to connect to the server. Use your local IP address instead. example: http://192.168.1.100:9000
Run ipconfig
to get your local IP address.
Step 2: Start Metro Server
npm start
Step 3: Run the Application
For Android:
npm run android
For iOS: Install dependencies for iOS:
npx pod-install ios
Run the application:
npm run ios
📱 Expo Usage
This project uses React Native CLI to ensure maximum flexibility for all developers. However, Expo users are more than welcome! You can easily add Expo support with a single command.
Learn more about migrating to Expo CLI
📁 Project Structure
app/
├── screens/ # Screen components
├── components/ # Reusable UI components
├── data/ # Data context providers
├── styles/ # Theme and style utilities
├── utils/ # Helper functions
└── api/ # API client configuration
📖 Developer Guide
Here’s the documentation for managing the core functionality of the app.
🛒 Cart Management
The cart functionality is provided through the useCart
hook, which gives you access to cart operations and state.
Basic Usage
import { useCart } from '@data/cart-context';
function MyComponent() {
const {
cart, // Current cart state
addToCart, // Add items to cart
updateLineItem, // Update item quantity
removeLineItem, // Remove item from cart
applyPromoCode, // Apply discount code
removePromoCode, // Remove discount code
setShippingMethod // Set shipping option
} = useCart();
}
Working with Cart Items
- Add a product to the cart:
const { addToCart } = useCart();
// Quantity is required when adding items
await addToCart(variantId, 1); // Add one item
await addToCart(variantId, 3); // Add three items
- Update item quantity:
const { updateLineItem } = useCart();
// Update to specific quantity
await updateLineItem(lineItemId, 2);
// Remove item by setting quantity to 0
await updateLineItem(lineItemId, 0);
Managing Promotions
const { applyPromoCode, removePromoCode } = useCart();
// Apply a promotion code
const success = await applyPromoCode('SUMMER2024');
// Remove a promotion code
await removePromoCode('SUMMER2024');
Shipping Methods
const { setShippingMethod } = useCart();
// Set shipping method
await setShippingMethod(shippingMethodId);
Accessing Cart Data
const { cart } = useCart();
// Get cart items
const items = cart.items;
// Get cart totals
const {
subtotal,
tax_total,
shipping_total,
discount_total,
total
} = cart;
// Check applied discounts
const appliedPromotions = cart.promotions;
// Get selected shipping method
const currentShipping = cart.shipping_methods?.[0];
Cart Lifecycle
The cart system handles various states and transitions:
- Cart Creation:
const { cart } = useCart();
// Cart is automatically created when needed
// You don't need to explicitly create a cart
- Guest to Customer Cart Transfer:
// When a guest user logs in, their existing cart is
// automatically associated with their customer account
// This is handled by the CartProvider and CustomerProvider
import { useCustomer } from '@data/customer-context';
import { useCart } from '@data/cart-context';
function CheckoutFlow() {
const { customer } = useCustomer();
const { cart } = useCart();
// Cart remains the same, only the customer_id is updated
}
- Cart update on region change:
import { useRegion } from '@data/region-context';
import { useCart } from '@data/cart-context';
function MyComponent() {
const { region } = useRegion();
const { cart } = useCart();
// Cart automatically updates when region changes
// Product prices will be updated based on the region
console.log(cart.region_id); // Current region ID
console.log(cart.currency_code); // Region's currency
}
🌍 Region Management
The region functionality is provided through the useRegion
hook, which handles region selection and persistence.
Basic Usage
import { useRegion } from '@data/region-context';
function MyComponent() {
const {
region, // Current selected region
setRegion, // Update region state
} = useRegion();
}
Working with Regions
- Access current region:
const { region } = useRegion();
// Get region details (if region is loaded)
const {
id,
name,
currency_code,
countries
} = region || {};
- Change region:
const { setRegion } = useRegion();
// Fetch region data first
const { region: newRegion } = await apiClient.store.region.retrieve(regionId);
// Update region
setRegion(newRegion);
// This will:
// - Persist region selection
// - Update cart region automatically
// - Trigger price recalculations
Region Selection UI
The app provides a built-in region selector modal:
import { useNavigation } from '@react-navigation/native';
function MyComponent() {
const navigation = useNavigation();
// Open region selector modal
const openRegionSelect = () => {
navigation.navigate('RegionSelect');
};
}
Working with Countries
The app provides a dedicated hook for accessing region countries:
import { useCountries } from '@data/region-context';
function AddressForm() {
const countries = useCountries();
// Format countries for picker/selector
const countryOptions = countries?.map(country => ({
label: country.display_name,
value: country.iso_2
}));
}
Region Persistence
Region selection is automatically persisted using AsyncStorage:
- On first load, defaults to the first available region
- On subsequent loads, restores the previously selected region
- Region ID is stored under the ‘region_id’ key
👤 Customer Management
The customer functionality is provided through the useCustomer
hook, which handles authentication and customer data management.
Basic Usage
import { useCustomer } from '@data/customer-context';
function MyComponent() {
const {
customer, // Current customer data
login, // Login with email/password
logout, // Logout current customer
register, // Register new customer
refreshCustomer, // Refresh customer data
updateCustomer // Update customer details
} = useCustomer();
}
Authentication
- Login:
const { login } = useCustomer();
try {
await login(email, password);
// On successful login:
// - JWT token is stored in AsyncStorage
// - Customer data is fetched
// - Cart is associated with customer
} catch (error) {
// Handle login error
}
- Register new customer:
const { register } = useCustomer();
try {
await register(
email,
password,
firstName,
lastName
);
// Registration automatically logs in the customer
} catch (error) {
// Handle registration error
}
- Logout:
const { logout } = useCustomer();
await logout();
// This will:
// - Clear the stored JWT token
// - Reset customer data
// - Reset cart
Managing Customer Data
- Access customer information:
import { useLoggedIn } from '@data/hooks';
function MyComponent() {
const { customer } = useCustomer();
const isLoggedIn = useLoggedIn();
// Access customer details
const {
email,
first_name,
last_name,
phone,
billing_address,
shipping_addresses
} = customer || {};
}
- Update customer details:
const { updateCustomer } = useCustomer();
// Update customer information
await updateCustomer({
first_name: "John",
last_name: "Doe",
phone: "+1234567890"
});
- Refresh customer data:
const { refreshCustomer } = useCustomer();
// Fetch latest customer data from server
await refreshCustomer();
Session Management
The customer session is automatically managed:
- JWT token is stored in AsyncStorage under ‘auth_token’
- Session is restored on app launch
- Token is automatically attached to API requests
- Session is cleared on logout
🎨 Theme Management
The app includes a flexible theming system with built-in light/dark mode support and multiple color schemes.
Basic Usage
import { useColors, useTheme, useThemeName, useColorScheme } from '@styles/hooks';
function MyComponent() {
const colors = useColors(); // Get current theme colors
const themeName = useThemeName(); // Get current theme name
const { colorScheme } = useColorScheme(); // Get 'light' or 'dark'
// Access theme colors
const {
primary, // Brand/accent color
background, // Main background
backgroundSecondary,// Secondary/card background
content, // Main text color
contentSecondary // Secondary text color
} = colors;
}
Setting Default Theme
// In app.tsx, set your preferred theme name in ThemeProvider
<ThemeProvider name="default">
{/* ... other providers */}
</ThemeProvider>
Available theme names:
- “default” (Purple accent)
- “vintage” (Warm red accent)
- “funky” (Teal accent)
- “eco” (Green accent)
Changing Themes
import { useTheme } from '@styles/hooks';
function ThemeSwitcher() {
const { setThemeName } = useTheme();
// Switch to a different theme
const switchTheme = (name: string) => {
setThemeName(name); // 'default' | 'vintage' | 'funky' | 'eco'
};
}
System Dark Mode
The theme system automatically responds to system dark mode changes through NativeWind’s useColorScheme
hook. Each theme includes both light and dark variants that are automatically applied based on the system setting.
Styling Components
The app uses NativeWind (TailwindCSS) for styling. Theme colors are available as Tailwind classes:
function ThemedButton() {
return (
<TouchableOpacity className="bg-primary"> // Theme primary color
<Text className="text-content font-bold"> // Theme content color
Click Me
</Text>
</TouchableOpacity>
);
}
🪝 Useful Hooks
The app provides additional hooks for common functionality:
import {
useProductQuantity,
useVariantQuantity,
useCartQuantity,
useCurrentCheckoutStep,
useActivePaymentSession,
useLoggedIn,
useCountries
} from '@data/hooks';
// Get quantity of a specific product in cart
const quantity = useProductQuantity(productId);
// Get quantity of a specific variant in cart
const variantQuantity = useVariantQuantity(variantId);
// Get total number of items in cart
const cartQuantity = useCartQuantity();
// Get current checkout step
const checkoutStep = useCurrentCheckoutStep();
// Returns: 'address' | 'delivery' | 'payment' | 'review'
// Get active payment session in checkout
const paymentSession = useActivePaymentSession();
// Check if user is logged in
const isLoggedIn = useLoggedIn();
// Get formatted list of countries for current region
const countries = useCountries();
// Returns: Array<{ label: string, value: string }>
Read the detailed documentation in the official repo of Medusa Mobile
Next Steps
Your Medusa mobile app is now running! Here’s what to do next:
Customize Your App
- Select one theme from the built-in themes, customize one of them, or build a new theme config to match your app requirement.
- Modify UI as needed. You have access to full source code andthe ability to change anything.
- Add custom features using Medusa’s API
Production Release
Follow React Native’s Release Guide for Android and iOS
Publishing to Google Play Store
Wrapping up
Start building your mobile commerce experience with React Native and Medusa today! Check out the GitHub repository of Medusa Mobile for the complete source code.
- Ultimate Guide: Build A Mobile E-commerce App With React Native And Medusa.js - February 15, 2025
- Flutter lookup failed in @fields error (solved) - July 14, 2023
- Free open source alternative to Notion along with AI - July 13, 2023