A Medusa region bundles a currency, a set of countries, tax behaviour, and the payment and fulfilment providers available. It is the unit of "selling here", and modelling it well is the difference between a clean international store and twelve half-maintained ones.
What a region holds
| Property | Effect |
|---|---|
| Currency | The currency prices are stored and charged in |
| Countries | Which shipping addresses belong to it |
| Tax behaviour | Inclusive or exclusive, and the applicable rates |
| Payment providers | What appears at checkout |
| Fulfilment options | Which shipping options are reachable |
| Automatic taxes | Whether tax is calculated at checkout |
How many regions?
One per currency plus commercial-rules combination. Not one per country.
A workable European setup:
| Region | Countries | Currency | Tax |
|---|---|---|---|
| Eurozone | DE, FR, IT, ES, NL, BE, … | EUR | Inclusive, per-country VAT |
| United Kingdom | GB | GBP | Inclusive, 20% |
| United States | US | USD | Exclusive, engine-calculated |
| Rest of world | Everything else | USD | Exclusive, no tax |
Sixteen eurozone countries, one region. They share a currency, similar consumer expectations and inclusive pricing; VAT differences are handled by tax regions, which are a separate concept and can be per country.
Split a region only when a country genuinely needs different payment methods, different fulfilment or a different currency. Each extra region is another price to maintain on every product, forever.
Setting them up
import { ExecArgs } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
export default async function setupRegions({ container }: ExecArgs) {
const region = container.resolve(Modules.REGION)
await region.createRegions([
{
name: "Europe",
currency_code: "eur",
countries: ["de", "fr", "it", "es", "nl", "be", "at", "ie", "pt", "fi"],
automatic_taxes: true,
},
{
name: "United Kingdom",
currency_code: "gbp",
countries: ["gb"],
automatic_taxes: true,
},
{
name: "United States",
currency_code: "usd",
countries: ["us"],
automatic_taxes: true,
},
])
}Then enable payment and fulfilment providers per region in the admin. That is how Stripe serves Europe while a local processor serves India, with no conditional logic in the storefront — see payment providers.
Pricing
Prices are per currency, explicitly. Medusa does not convert automatically, and that is correct: international pricing is a commercial decision, not an arithmetic one.
prices: [
{ amount: 4900, currency_code: "usd" },
{ amount: 4500, currency_code: "eur" }, // not 4900 converted
{ amount: 3900, currency_code: "gbp" },
]Three reasons psychological pricing beats conversion: round prices convert better than €45.83, exchange rates move and you do not want your catalog moving with them, and different markets bear different prices legitimately.
For inclusive-tax regions, remember the entered price is the gross price. €45 in an inclusive region is €45 at checkout with VAT inside it. Tax configuration.
Storefront routing
Country in the URL, region resolved from it:
/us/products/leather-wallet → United States region, USD
/de/products/leather-wallet → Europe region, EUR
/gb/products/leather-wallet → United Kingdom region, GBPexport async function generateStaticParams() {
const { regions } = await sdk.store.region.list()
return regions.flatMap((r) => (r.countries ?? []).map((c) => ({ countryCode: c.iso_2 })))
}Every country gets a crawlable, cacheable URL with the right currency baked in. Language is a separate axis — see multi-language storefronts.
Do not force redirects based on IP. Suggest, remember the choice, and let people override — crawlers and travellers both need access to every version.
Availability per market
Two mechanisms, often confused:
- Sales channels control which products exist in a channel. Use them for genuinely different catalogs.
- Fulfilment service zones control where things can ship. Use them for "we sell this but not to Norway".
For a product that must not be sold in one country at all — regulatory restrictions, distribution agreements — sales channels plus a check in the checkout workflow is the reliable combination.
Common mistakes
A region per country. Sixteen eurozone regions means sixteen prices per product and sixteen chances to forget one.
Converting prices automatically. Produces €45.83 and a catalog that shifts with the exchange rate.
Conflating region and language. A Belgian customer may want French or Dutch; both are the eurozone region.
Forgetting inclusive versus exclusive. Entering net prices in an inclusive region silently undercharges by the VAT rate. That one is expensive.
Currency switching in the storefront
When a customer changes country, three things must happen together, and missing one produces the bug where prices change but the cart does not.
"use server"
export async function switchCountry(countryCode: string) {
const region = await getRegionByCountry(countryCode)
const cart = await getCart()
if (cart && cart.region_id !== region.id) {
// The cart must move regions too, or its line totals stay in the old currency.
await sdk.store.cart.update(cart.id, { region_id: region.id })
}
const store = await cookies()
store.set("_country", countryCode, { maxAge: 60 * 60 * 24 * 365, path: "/" })
revalidateTag("cart")
redirect(`/${countryCode}`)
}Also handle the case where an item is unavailable in the new region — Medusa will remove or fail on it, and the customer needs to be told which item went rather than discovering a smaller basket.
Launching a new market
A checklist, because the order matters:
[ ] Region created with the correct currency and countries
[ ] Prices set for the new currency on every active variant
[ ] Tax region and rates configured, inclusive or exclusive decided
[ ] Payment providers enabled and tested with a real card
[ ] Service zone covers the countries; shipping options priced
[ ] Storefront route and country selector updated
[ ] Legal pages reviewed for the market
[ ] Customer service can support the timezone and languageThe one that catches people is the second: a variant with no price in the new currency is silently unbuyable, with no error anywhere. Run a coverage query before announcing the launch, not after the first complaint.
Region and inventory
A region says where you sell. It does not say what you have. Those are separate concerns, and conflating them produces overselling.
Inventory lives at stock locations and is checked at add-to-cart and again at completion, against the location that will fulfil the order. So a European customer and an American customer buying the last unit are competing for the same stock unless you hold separate inventory per location.
The practical decisions: whether markets share pooled inventory or hold their own, and whether a region can fulfil from a location on another continent. Both are commercial questions with a technical expression, and both are much easier to answer before launch than after the first oversell.
International setup is easy to get right at the start and tedious to fix later. Ask us to review one.
Frequently asked questions
How many regions should a Medusa store have?
One per currency plus commercial-rules combination, not one per country. Sixteen eurozone countries with the same currency and similar rules belong in a single region; VAT differences are handled separately by tax regions.
Does Medusa convert prices between currencies automatically?
No, deliberately. You set an explicit price per currency, so pricing stays a commercial decision and your catalog does not move with the exchange rate. It also lets you use round, psychologically effective prices in each market.
What is the difference between a region and a sales channel?
A region defines commercial rules — currency, tax, available payment and shipping methods. A sales channel defines which products are available in a given context. Use regions for how you sell, and sales channels for what you sell.
How should the storefront handle multiple regions?
Put the country code in the URL and resolve the region from it, so each market has a distinct, cacheable, crawlable URL with the correct currency and tax treatment. Suggest a region based on location rather than forcing a redirect.
Can different regions use different payment providers?
Yes. Providers are registered globally and enabled per region, so Europe can use Stripe while India uses a local processor, with no conditional logic in the storefront.
How do I stop a product being sold in one country?
Use sales channels to control catalog availability per market, and add a validation step in the checkout workflow for hard regulatory restrictions. Fulfilment service zones control where you ship, which is a related but different question.
Multi-Currency Pricing in Medusa: Price Lists and Rounding
Explicit prices per currency, price lists for sales and customer-group pricing, and the minor-unit arithmetic that makes zero-decimal currencies interesting.
PayPal with Medusa: Integration, Buttons and Dispute Handling
Adding PayPal alongside cards, wiring the JS SDK buttons into a Next.js checkout, and the dispute and webhook behaviour that differs from a card processor.
Multi-Language Medusa Storefronts: Routing, Content and hreflang
Medusa handles currency and tax per region, not translation. How to layer language onto that — URL structure, product content, and the hreflang most stores get wrong.

