Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Implement Multi-Currency Support in FlutterFlow

Store all prices in USD cents in Firestore to avoid conversion inconsistencies. Use a Cloud Function to fetch daily exchange rates and write them to a Firestore 'currencies' collection. Build a formatPrice Custom Function using Dart's NumberFormat.currency to display the correct symbol and formatting per locale. Wire the user's selected currency to App State so every price widget updates automatically.

What you'll learn

  • Why to store all prices in USD cents and convert only at display time
  • How to fetch and cache daily exchange rates in Firestore via Cloud Function
  • How to write a formatPrice Custom Function using NumberFormat.currency
  • How to integrate Stripe Checkout with the user's selected currency
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner10 min read45-60 minFlutterFlow Pro+ (Custom Functions and API integrations required)March 2026RapidDev Engineering Team
TL;DR

Store all prices in USD cents in Firestore to avoid conversion inconsistencies. Use a Cloud Function to fetch daily exchange rates and write them to a Firestore 'currencies' collection. Build a formatPrice Custom Function using Dart's NumberFormat.currency to display the correct symbol and formatting per locale. Wire the user's selected currency to App State so every price widget updates automatically.

One Source of Truth for Every Price

Multi-currency support sounds simple — multiply a price by an exchange rate. But doing this wrong causes prices to drift as rates change, Stripe charges that do not match what the user saw, and rounding errors that add up at scale. The correct architecture stores every price in a single baseline currency (USD cents, an integer with no floating-point issues), fetches rates once per day from an exchange rate API, and converts only when rendering the price on screen. This guide wires that pattern into FlutterFlow from Firestore through to Stripe.

Prerequisites

  • FlutterFlow Pro plan (Custom Functions required)
  • Firebase project with Firestore enabled
  • Exchange rate API key (exchangerate-api.com free tier works for development)
  • Basic understanding of Firestore collections and App State

Step-by-step guide

1

Set Up the Currencies Collection in Firestore

Create a Firestore collection called 'currencies' where each document ID is a currency code (USD, EUR, GBP, JPY, etc.). Each document needs four fields: code (String), symbol (String), rate_to_usd (Double — how many USD equal one unit of this currency, inverted for display), and updated_at (Timestamp). Seed it with a few initial rates so your app has something to display while the Cloud Function runs for the first time. Also create a 'currency_config' document in a 'app_config' collection with a list of supported_currencies and a base_currency field set to 'USD'. In FlutterFlow, add a Firestore collection schema for 'currencies' in your project settings.

Expected result: Firestore shows a 'currencies' collection with documents like 'EUR' containing code: EUR, symbol: €, rate_to_usd: 0.92, updated_at: now.

2

Create a Cloud Function to Refresh Exchange Rates Daily

In Firebase Console, create a scheduled Cloud Function (via Cloud Scheduler) that runs once per day. The function fetches the latest rates from your exchange rate API and updates each currency document in Firestore. Using a server-side function instead of a client-side API call keeps your API key secret and ensures all users see the same rates refreshed at the same time. The function should update only the rate_to_usd and updated_at fields to avoid overwriting any manual configuration on other fields.

refresh_exchange_rates.js
1const functions = require('firebase-functions');
2const admin = require('firebase-admin');
3const axios = require('axios');
4
5exports.refreshExchangeRates = functions.pubsub
6 .schedule('0 6 * * *')
7 .timeZone('UTC')
8 .onRun(async () => {
9 const apiKey = process.env.EXCHANGE_RATE_API_KEY;
10 const base = 'USD';
11 const url = `https://v6.exchangerate-api.com/v6/${apiKey}/latest/${base}`;
12
13 const response = await axios.get(url);
14 const rates = response.data.conversion_rates;
15
16 const db = admin.firestore();
17 const batch = db.batch();
18
19 const supportedCurrencies = ['USD', 'EUR', 'GBP', 'JPY', 'CAD', 'AUD', 'INR', 'BRL'];
20
21 for (const code of supportedCurrencies) {
22 if (rates[code]) {
23 const ref = db.collection('currencies').doc(code);
24 batch.update(ref, {
25 rate_to_usd: rates[code],
26 updated_at: admin.firestore.FieldValue.serverTimestamp(),
27 });
28 }
29 }
30
31 await batch.commit();
32 return null;
33 });

Expected result: Cloud Function logs show successful rate refresh each morning and Firestore currency documents show today's date in updated_at.

3

Build the formatPrice Custom Function

In FlutterFlow, open Custom Code > Custom Functions and create a new function called formatPrice. It takes three parameters: priceInUsdCents (integer), currencyCode (String), and rateToUsd (double). The function converts from cents to the display currency and formats it using Dart's intl package NumberFormat.currency with the correct locale and symbol. Returning a formatted string like '€12.50' or '¥1,350' keeps all price rendering logic in one reusable place across every widget in your app.

format_price.dart
1import 'package:intl/intl.dart';
2
3String formatPrice(
4 int priceInUsdCents,
5 String currencyCode,
6 double rateToUsd,
7) {
8 // Convert cents to dollars, then to target currency
9 final double usdAmount = priceInUsdCents / 100.0;
10 final double displayAmount = usdAmount * rateToUsd;
11
12 // Map currency codes to locales for correct formatting
13 const Map<String, String> currencyLocales = {
14 'USD': 'en_US',
15 'EUR': 'de_DE',
16 'GBP': 'en_GB',
17 'JPY': 'ja_JP',
18 'CAD': 'en_CA',
19 'AUD': 'en_AU',
20 'INR': 'hi_IN',
21 'BRL': 'pt_BR',
22 };
23
24 final String locale = currencyLocales[currencyCode] ?? 'en_US';
25
26 // JPY has no decimal places
27 final int decimalDigits = currencyCode == 'JPY' ? 0 : 2;
28
29 final formatter = NumberFormat.currency(
30 locale: locale,
31 symbol: '',
32 decimalDigits: decimalDigits,
33 );
34
35 final symbols = {
36 'USD': r'$', 'EUR': '€', 'GBP': '£',
37 'JPY': '¥', 'CAD': r'CA$', 'AUD': r'A$',
38 'INR': '₹', 'BRL': r'R$',
39 };
40
41 final symbol = symbols[currencyCode] ?? currencyCode + ' ';
42 return '$symbol${formatter.format(displayAmount)}';
43}

Expected result: Calling formatPrice(1999, 'EUR', 0.92) returns '€18.39' with correct European decimal formatting.

4

Wire Currency Selection to App State

Add three App State variables: selectedCurrencyCode (String, default: USD), selectedCurrencySymbol (String, default: $), and selectedCurrencyRate (double, default: 1.0). On your profile or settings page, add a DropdownButton widget populated from your Firestore currencies collection. When the user picks a currency, update all three App State variables in the On Change action. On every price Text widget, replace the static value with the formatPrice Custom Function, passing the product price field, AppState.selectedCurrencyCode, and AppState.selectedCurrencyRate. The prices will update across the whole app instantly whenever the user changes currency.

Expected result: Switching from USD to EUR in the settings dropdown immediately updates all product prices across every page to show euro amounts with the correct symbol.

5

Pass the Selected Currency to Stripe Checkout

When creating a Stripe PaymentIntent or Checkout Session in your Cloud Function, pass the user's selected currency code and calculate the amount in the target currency. Stripe accepts amounts as the smallest currency unit (cents for USD/EUR, yen for JPY), so multiply the display amount accordingly. Important: always re-fetch the current rate from Firestore in the Cloud Function at checkout time rather than trusting the client-sent rate, which could be tampered with. This ensures the charge matches what the user saw within the tolerance of the day's exchange rate.

create_checkout_session.js
1// Cloud Function — create Stripe Checkout Session
2exports.createCheckoutSession = functions.https.onCall(async (data, context) => {
3 const { priceInUsdCents, currencyCode, successUrl, cancelUrl } = data;
4 const userId = context.auth?.uid;
5 if (!userId) throw new functions.https.HttpsError('unauthenticated', 'Login required');
6
7 // Fetch current rate from Firestore (never trust client-sent rate)
8 const currencyDoc = await admin.firestore()
9 .collection('currencies').doc(currencyCode).get();
10 const rateToUsd = currencyDoc.data()?.rate_to_usd ?? 1.0;
11
12 const usdAmount = priceInUsdCents / 100;
13 const displayAmount = usdAmount * rateToUsd;
14
15 // Stripe uses smallest unit — JPY has no cents
16 const stripeAmount = currencyCode === 'JPY'
17 ? Math.round(displayAmount)
18 : Math.round(displayAmount * 100);
19
20 const session = await stripe.checkout.sessions.create({
21 payment_method_types: ['card'],
22 line_items: [{
23 price_data: {
24 currency: currencyCode.toLowerCase(),
25 unit_amount: stripeAmount,
26 product_data: { name: data.productName },
27 },
28 quantity: 1,
29 }],
30 mode: 'payment',
31 success_url: successUrl,
32 cancel_url: cancelUrl,
33 });
34
35 return { sessionId: session.id, url: session.url };
36});

Expected result: Stripe Checkout opens in the user's selected currency and shows the correctly converted amount that matches the in-app price they saw.

Complete working example

format_price.dart
1// FlutterFlow Custom Function: formatPrice
2// Parameters:
3// priceInUsdCents — int (e.g. 1999 = $19.99 USD)
4// currencyCode — String (e.g. 'EUR')
5// rateToUsd — double (e.g. 0.92 means 1 USD = 0.92 EUR)
6// Returns: String formatted price with symbol
7
8import 'package:intl/intl.dart';
9
10String formatPrice(
11 int priceInUsdCents,
12 String currencyCode,
13 double rateToUsd,
14) {
15 if (priceInUsdCents <= 0) return 'Free';
16 if (rateToUsd <= 0) rateToUsd = 1.0;
17
18 final double usdAmount = priceInUsdCents / 100.0;
19 final double displayAmount = usdAmount * rateToUsd;
20
21 const Map<String, String> currencyLocales = {
22 'USD': 'en_US',
23 'EUR': 'de_DE',
24 'GBP': 'en_GB',
25 'JPY': 'ja_JP',
26 'CAD': 'en_CA',
27 'AUD': 'en_AU',
28 'INR': 'hi_IN',
29 'BRL': 'pt_BR',
30 'CHF': 'de_CH',
31 'SGD': 'en_SG',
32 };
33
34 const Map<String, String> symbols = {
35 'USD': r'$',
36 'EUR': '€',
37 'GBP': '£',
38 'JPY': '¥',
39 'CAD': r'CA$',
40 'AUD': r'A$',
41 'INR': '₹',
42 'BRL': r'R$',
43 'CHF': 'CHF ',
44 'SGD': r'S$',
45 };
46
47 // Currencies with no decimal places
48 const Set<String> zeroCentCurrencies = {'JPY', 'KRW', 'VND', 'IDR'};
49 final int decimalDigits = zeroCentCurrencies.contains(currencyCode) ? 0 : 2;
50
51 final String locale = currencyLocales[currencyCode] ?? 'en_US';
52
53 final formatter = NumberFormat.currency(
54 locale: locale,
55 symbol: '',
56 decimalDigits: decimalDigits,
57 );
58
59 final String symbol = symbols[currencyCode] ?? '$currencyCode ';
60 return '$symbol${formatter.format(displayAmount)}';
61}
62
63// ─── Usage Examples ────────────────────────────────────────────────────────
64// formatPrice(1999, 'USD', 1.0) → '$19.99'
65// formatPrice(1999, 'EUR', 0.92) → '€18.39'
66// formatPrice(1999, 'JPY', 150) → '¥2,999'
67// formatPrice(0, 'USD', 1.0) → 'Free'

Common mistakes when implementing Multi-Currency Support in FlutterFlow

Why it's a problem: Converting prices on the server and storing the converted amounts in Firestore

How to avoid: Store all prices as USD cents in Firestore. Convert only at display time using the current rate from your currencies collection. The single source of truth never changes.

Why it's a problem: Using floating-point doubles to store monetary amounts

How to avoid: Store prices as integers representing the smallest currency unit (cents for USD). Only convert to a double when displaying or passing to Stripe, and apply Math.round() before creating a charge.

Why it's a problem: Trusting the currency rate sent from the client app when creating a Stripe charge

How to avoid: Always re-fetch the current exchange rate from Firestore inside your Cloud Function when creating a Stripe PaymentIntent. Never trust any financial calculation that originates on the client.

Best practices

  • Store all prices as integer USD cents in Firestore — this is the single source of truth that never changes with exchange rate fluctuations.
  • Refresh exchange rates server-side via Cloud Function once per day — more frequent updates are unnecessary for retail use cases and cost extra API calls.
  • Display the last_updated date of exchange rates somewhere in your checkout flow so users understand prices may vary slightly with currency fluctuations.
  • Handle zero-decimal currencies like JPY and KRW explicitly — Stripe and NumberFormat both need different treatment for these.
  • Cache the selected currency and rate in App State so you are not reading Firestore on every price widget render.
  • Add a 'default_currency' field to your user document so returning users see their preferred currency automatically without having to select it again.
  • Test your Stripe integration in test mode with at least 3 different currencies before going live, including a zero-decimal currency if you support any.

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I am building a FlutterFlow app that sells products globally. Explain the correct architecture for multi-currency support: where to store prices, how to fetch and cache exchange rates, how to format currency with the correct locale and symbol in Dart using NumberFormat.currency, and how to pass the correct currency amount to Stripe Checkout without trusting client-sent data.

FlutterFlow Prompt

In my FlutterFlow app, create a Custom Function called formatPrice that takes priceInUsdCents (int), currencyCode (String), and rateToUsd (double) and returns a formatted currency string with the correct symbol. Use Dart's NumberFormat.currency and handle JPY and other zero-decimal currencies. Also create App State variables selectedCurrencyCode, selectedCurrencySymbol, and selectedCurrencyRate.

Frequently asked questions

Which exchange rate API works best for FlutterFlow apps?

ExchangeRate-API (exchangerate-api.com) has a generous free tier (1,500 requests/month), simple JSON response format, and reliable uptime. For production apps with higher volume, Open Exchange Rates or Fixer.io offer more history and reliability. Call the API from a Cloud Function, not from your FlutterFlow app directly, to protect your API key.

Does Stripe support all the currencies I want to offer?

Stripe supports 135+ currencies. Check stripe.com/docs/currencies for the full list. Some currencies are presentment-only (you can display them but are actually charged in a different currency). Review the minimum charge amounts per currency — some have higher minimums than USD.

How do I handle currency selection for guest (unauthenticated) users?

Store the selected currency in FlutterFlow's App State (persisted to local storage) rather than the Firestore user document. On app start, read the stored currency from local storage and initialize the App State variables. When the user signs in, sync the local selection to their Firestore document.

What happens if the exchange rate API is down when my Cloud Function runs?

Add error handling in your Cloud Function that catches API failures and skips the Firestore update rather than overwriting rates with zeros. Your app will continue displaying the previous day's rates, which is acceptable for a 24-hour outage. Log the failure to a Firestore error document so you can monitor it.

Should I show prices inclusive or exclusive of VAT in different countries?

This is a legal and business decision, not purely technical. EU consumers expect VAT-inclusive prices; US consumers expect pre-tax prices. Consider adding a vat_rate field per currency/region and a showPriceWithVat boolean in your app config. This is separate from currency conversion and should be handled after you have the base currency conversion working correctly.

Why do my prices look different in the Stripe Dashboard versus what users see in my app?

Stripe Dashboard shows the charge amount in the currency you passed to the PaymentIntent. If your app display uses a slightly different exchange rate than the one used at checkout time (e.g., App State cached an old rate), you will see rounding differences. Always re-fetch the rate from Firestore in your Cloud Function at checkout time to ensure the displayed price and the charge are calculated from the same rate.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.