Tutorial 5 min read

Flutter Medusa E-commerce App: Open-Source Storefront for Medusa v2

In short: Medusa Mobile for Flutter is an open-source (MIT) Flutter storefront for Medusa v2. Clone it, put your backend URL and publishable API key in .env, and run flutter run --dart-define-from-file=.env. You get product browsing, search, cart, guest checkout with manual payment or Stripe, accounts, regions and four themes, for iOS and Android.

It is a port of Medusa Mobile for React Native, with the same screens and design, for teams that write Dart instead of TypeScript. The code is on GitHub at bloomsynth/medusa-mobile-flutter.

In this guide:

What you get

Medusa is an open-source headless commerce engine: it runs your products, carts, orders and payments, and you bring the storefront. This app is that storefront, as a Flutter app:

  • Product browsing with infinite scroll, categories and collections
  • Product search
  • Product pages with variant selection, stock rules and related products
  • Cart with quantities and promo codes
  • Guest checkout in four steps: address, delivery, payment, review
  • Manual payment and Stripe (Payment Sheet)
  • Sign-in, registration, profile, saved addresses and order history
  • Multiple regions with region prices
  • Four built-in themes, each with light and dark mode
  • English and Indonesian

The app talks to Medusa’s Store API with a publishable key only. Admin credentials and secret keys never go in the app.

Set up the app

You need Flutter 3.47 or newer (Dart 3.13+) and a running Medusa v2 backend. If you don’t have one yet, follow the Medusa installation guide, or the backend section of our React Native Medusa tutorial, which works the same for this app. The app is tested with the Medusa 2.22.1 starter backend.

Clone the repository and create your .env:

git clone https://github.com/bloomsynth/medusa-mobile-flutter.git
cd medusa-mobile-flutter
cp .env.template .env

Set MEDUSA_BACKEND_URL and PUBLISHABLE_API_KEY in .env. The publishable key must be linked to the sales channel that holds your products. Then install the packages and run it:

flutter pub get
flutter run --dart-define-from-file=.env

The values are compiled into the app with --dart-define-from-file, so rebuild after you change them. The backend URL depends on where the app runs:

Where the app runs Backend URL for a local Medusa
Android Studio emulator http://10.0.2.2:9000 (the template default)
iOS simulator http://localhost:9000
Physical Android device http://localhost:9000 after adb reverse tcp:9000 tcp:9000, or your computer’s LAN IP

Plain http works in Android debug builds and for local addresses on iOS. Release builds need an https backend. Two other optional values: DEFAULT_LOCALE (en-US or id-ID) and STRIPE_PUBLISHABLE_KEY.

Add Stripe

Checkout pays through the Stripe Payment Sheet, with the flutter_stripe package. On the backend, register Medusa’s Stripe provider with your secret key and enable pp_stripe_stripe for the region. In the app, set only STRIPE_PUBLISHABLE_KEY in .env; leave it empty and card payments stay off. Use test keys and Stripe’s test cards while you build.

How the app is organised

Folders are by feature, and each feature keeps its cubits (flutter_bloc), widgets and screens together. Routing is go_router, the HTTP client is Dio, and API models are generated with freezed.

  • lib/api/ holds MedusaClient, one class per Store API area, and the models.
  • lib/region/, lib/cart/ and lib/customer/ hold the shared state: the selected region, the cart, and the signed-in customer.
  • lib/catalog/, lib/checkout/, lib/orders/, lib/address/ and lib/settings/ hold the screens.
  • lib/theme/ holds the color tokens and themes; lib/l10n/arb/ holds the copy.

The one rule worth knowing before you change anything: every cart change goes through CartCubit, which keeps the cart the server sends back. CartCubit listens to RegionCubit and moves the cart when the region changes, and CustomerCubit moves the guest cart to the account on sign-in. Keep a second copy of the cart somewhere else and totals and checkout steps go stale.

Customize it

  • Themes: edit or add a color set in lib/theme/app_themes.dart.
  • Copy and languages: edit the ARB files in lib/l10n/arb/, run flutter gen-l10n, and add a new locale to LocaleCubit.supportedLocales.
  • Payment providers: add the provider to paymentProviderDetails in lib/checkout/models/checkout_models.dart and its payment step to CheckoutCubit.completeOrder.
  • Home banners: HeroCarousel reads sample JSON; point it at your own endpoint.

After you change a model in lib/api/models, run dart run build_runner build.

Run the tests

flutter test runs the unit tests: the real client, models and cubits against a fake Store API, with Stripe replaced by a fake. They cover the API client, cart, checkout guards, payment results, prices, variant selection and stock rules.

The end-to-end tests in integration_test/ drive the real app on a device against a real backend. They place an order and register a new customer on every run, so point them at a development backend only:

flutter test integration_test/checkout_flow_test.dart --dart-define-from-file=.env -d DEVICE_ID

The app has been checked this way on Android against the Medusa 2.22.1 starter backend. Not checked yet: a real test payment through the Stripe Payment Sheet, and iOS, which is set up but has not been built. Check those on a device before you ship. If you build on Windows with the project and the pub cache on different drives, the repository already turns off Kotlin incremental compilation, which fails in that setup.

Flutter or React Native?

Both apps cover the same shopping flow against the same backend, so pick the language your team already writes. The React Native version uses TypeScript, React Query and NativeWind, and has a generator, npx create-medusa-mobile my-store, that renames the app for you. The Flutter version uses Dart and cubits, and you start by cloning it. A few small things differ: in the Flutter app, saved addresses use the checkout’s address form, returning to checkout resumes at Payment, and order dates show as dates rather than relative times.

Both are listed on the Medusa Mobile site. If you are new to Flutter tooling, our post on reporting non-fatal errors to Crashlytics in Flutter is a good next step for a store app.

FAQ

Is there a Flutter storefront for Medusa v2? Yes. Medusa Mobile for Flutter is an MIT-licensed Flutter storefront for Medusa v2, tested with Medusa 2.22.1.

Is it free? Yes. It is open source under the MIT license, so you can use and change it for your own store.

Does it work with Medusa v1? No. It uses the Medusa v2 Store API.

Can I use it for a real store? Yes, once you have checked your payment flow and iOS build on a device, put your backend behind https, and replaced the sample banners and themes with your own.