Shopify Multi-Currency Cart Drawer: Setup & Fixes (2026)
Short answer: A Shopify cart drawer shows local currency automatically when three things are true: you sell in that currency through Shopify Markets, your theme prints money with Liquid's money filters rather than hardcoded values, and every AJAX cart update re-renders the drawer through the Section Rendering API instead of patching prices in JavaScript. When a drawer "flips back to USD" after an add-to-cart, it is almost always the third — or a currency-converter app rewriting the DOM after the drawer already redrew itself.
Key Facts (Verified September 2026)
- Local currency pricing lives in Shopify Markets, and Shopify's docs state that only stores on Shopify Payments with one-page checkout can use all functionality of the feature.
cart.currencyreturns the customer's presentment currency when the store uses multi-currency — per the cart object reference — andcart.total_priceis in that currency's subunit.- The Section Rendering API renders up to five sections per request. Dawn passes a
sectionsarray on every/cart/change.jscall, which is why its drawer keeps the right currency. - Dawn ships the country picker as
snippets/country-localization.liquidinside a{%- form 'localization' -%}block; Horizon consolidates both pickers intosnippets/localization-form.liquid. - Since May 7, 2026, Shopify discounts — including Buy X Get Y — can be assigned to specific markets natively.
- The US $800 de minimis exemption ended August 29, 2025; duties now apply to all US imports regardless of value. The EU still waives duty under €150.

How Shopify Actually Sells in Local Currencies
Everything downstream — the drawer, the shipping bar, the upsell price — reads from one source: the active market. Get that layer wrong and no amount of theme editing will fix the drawer.
Markets is the control panel
In Shopify admin → Settings → Markets, each market is a set of countries plus the currency, catalog and pricing rules that apply to them. Selling in local currency is a per-market choice, not a store-wide switch. Shopify converts your base prices at the current market exchange rate, and you can set rates manually per currency if you would rather not float.
Rounding makes converted prices look deliberate
Raw conversion produces prices like €54.27. Shopify's price rounding setting fixes the ending so prices stay stable while the exchange rate moves underneath. Set it at Markets → [your market] → Currency → Price rounding; Shopify pre-fills the most common denominator for that currency. Turn it on before you tune anything in the drawer — a bar reading "Add €5.73 for free shipping" is usually a rounding problem, not a drawer problem.
Price adjustments let you stop being a currency converter
Each market's Pricing settings take a percentage adjustment — up 10% where a market costs more to serve, down where you need to compete — and you can override individual product prices with a market-specific price list. Your upsell economics change with it: a gift-wrap add-on that is a comfortable $5 in the US is a badly-priced €4.63 in the EU unless adjustment and rounding are deliberate.
What happens without Shopify Payments
Shopify is explicit that full local-pricing functionality requires Shopify Payments with one-page checkout. On a third-party gateway you lose parts of the feature, and the safe assumption is that the customer is charged in your base currency even if the storefront showed something else.
A shopper who sees €48.00 in the drawer and a USD charge on their statement files a dispute. If you cannot use Shopify Payments, sell that market in one clearly-labelled currency rather than displaying a converted price you cannot honour at checkout.
How the Storefront Decides Which Currency to Show
Shopify resolves the active market before your theme renders anything. Three inputs decide it:
- Domain or subfolder. A market's own domain (
yourstore.de) or subfolder (yourstore.com/de-de) is the market — the strongest signal, and the one search engines index. - Geolocation. Shopify's geolocation app recommends a market to first-time visitors by IP, usually as a dismissible banner rather than a forced redirect.
- Explicit selection. The shopper picks a country in the localization form; that choice is stored and wins on later visits.
Inside Liquid, the result is the localization object, which exposes exactly five properties: available_countries, available_languages, country, language and market. The country object it returns carries iso_code, name, currency, market, available_languages, unit_system and popular? — so localization.country.currency.iso_code is the canonical "what currency is this shopper seeing" value, with no JavaScript required.
To change it, a theme submits a {% form 'localization' %} with a hidden country_code input. Dawn's snippet ends with exactly that line:
<input type="hidden" name="country_code" value="{{ localization.country.iso_code }}">
Every country button rewrites that hidden value and submits the form. There is no client-side conversion anywhere in the flow — the page reloads in the new market, which is why a correctly built selector never desyncs.
How the Cart Drawer Renders Money
Shopify stores prices as integers in the currency's subunit. A cart.total_price of 1000 is ten dollars, euros or pounds depending on the presentment currency — the integer alone tells you nothing. The Liquid money filters do the formatting.
| Filter | Output for 1000 | Use it when |
|---|---|---|
money | $10.00 | Line items and subtotals in a single-currency store, or anywhere the currency is unambiguous from context. |
money_with_currency | $10.00 CAD | Any store where two markets share a symbol — USD/CAD/AUD all print "$". This is the drawer default for international stores. |
money_without_currency | 10.00 | Feeding a number into JavaScript, a progress-bar calculation, or a data attribute. |
money_without_trailing_zeros | $10 | Compact badges and upsell chips where decimals add noise. |
Outputs per Shopify's filter reference. Note what these filters do not do: they never convert. They format whatever integer Shopify already resolved for the active market. If the number is wrong, the market is wrong — changing the filter only changes the symbol printed beside a wrong amount.
Practical rules for a drawer:
- Print
cart.total_price | money_with_currencyin the drawer footer if you sell into more than one dollar-symbol market. Ambiguous "$" totals cause disputes. - Never hardcode a symbol in a theme string or app setting — let the filter emit it.
- For progress bars, keep the maths in subunits and format only at the last step. Parsing "€1.234,56" back into a float is where decimal bugs are born.
- Branch on currency with
localization.country.currency.iso_code, server-side, in Liquid.
Putting a Currency Selector Inside the Cart Drawer
Most themes put the country picker in the footer — exactly where nobody looks after adding to cart. Dawn renders it from snippets/country-localization.liquid, wrapped in a <localization-form> custom element and a {%- form 'localization' -%} tag. Horizon does the same job with a single snippets/localization-form.liquid taking show_country and show_language parameters. Either way the snippet already exists — you are relocating it, not writing it.
In Dawn, add this inside sections/cart-drawer.liquid, near the footer block:
{%- if localization.available_countries.size > 1 -%}
<localization-form class="cart-drawer__localization">
{%- form 'localization', id: 'CartDrawerCountryForm', class: 'localization-form' -%}
<h2 class="visually-hidden" id="CartDrawerCountryLabel">
{{ 'localization.country_label' | t }}
</h2>
{%- render 'country-localization', localPosition: 'CartDrawerCountry' -%}
{%- endform -%}
</localization-form>
{%- endif -%}
Horizon's equivalent is one line: {% render 'localization-form', show_country: true, show_language: false, form_id: section.id, localization_style: 'dropdown' %}.
Two caveats. The localPosition / form_id value must be unique — Dawn builds element IDs from it, and reusing the footer's value gives you duplicate IDs and a picker that opens the wrong panel. And submitting the form reloads the page, so the drawer closes; that is correct, because the cart is re-costed in the new market.
On placement, the rules in our guide to customising the Shopify cart drawer apply: the selector belongs below the subtotal, above the checkout button, and should never compete with the primary CTA.
Cart Drawer Showing the Wrong Currency: Five Causes and Fixes

1. The drawer reverts to store currency after an AJAX add
The classic: the product page is correct, you add to cart, and the drawer opens in USD.
Cause: the drawer is repainted from a raw /cart/add.js or /cart/change.js JSON response. Those give you integers and line-item data, not your theme's formatted, market-aware HTML. Any code that takes item.final_price and prepends a symbol from a JavaScript constant prints the wrong currency the moment the shopper leaves your home market.
Fix: re-render server-side. Dawn's cart JavaScript builds its request body as { line, quantity, sections: sectionsToRender.map(s => s.section), sections_url: window.location.pathname } — the sections parameter is what makes the response include fully rendered drawer HTML in the active market. Swap your innerHTML for parsedState.sections[sectionId] and the problem disappears, because Liquid did the formatting.
Two constraints. The Section Rendering API renders at most five sections per request — a drawer, header count, shipping bar, upsell block and footer note is already your whole budget, so consolidate before you hit the ceiling. And sections_url must be window.location.pathname, not a hardcoded /, or sections render in the market that owns the root path rather than the shopper's.
2. Hardcoded /cart and /checkout paths
Cause: subfolder markets live under a prefix like /de-de. A fetch to a literal /cart/add.js hits the root market, resolves in your base currency, and can silently split the shopper's cart.
Fix: use the routes object everywhere — routes.cart_url and routes.cart_add_url in Liquid, window.Shopify.routes.root + 'cart.js' in JavaScript. Both carry the market prefix. Grep your theme for "/cart and "/checkout; a single hit breaks the flow.
3. A currency-converter app fighting the drawer
Cause: converter apps scan the DOM for price nodes and rewrite their text; your drawer re-renders through the Section Rendering API. The two run on different clocks, so you get whichever finished last — often correct on first paint and wrong after a quantity change, or the reverse.
Fix: on Shopify Payments and Markets you do not need a converter at all — Markets prices in local currency and, unlike a display-only converter, carries that price to checkout. Uninstall it, confirm no leftover script tag, retest. If you truly cannot use Shopify Payments, label converted prices as approximate and never let the converter rewrite the drawer subtotal.
4. Free-shipping thresholds set in store currency
Cause: the bar is configured with one number — 6000 subunits meaning $60 — then displayed against a converted cart total. The German shopper sees "Add €12.43 for free shipping" against a threshold matching no shipping rate you actually configured.
Fix: two halves, both required.
- The shipping rate is per zone in Settings → Shipping and delivery. Create a price-based rate per zone with a round local number: $60 US, £45 UK, €55 EU. Those are the real thresholds.
- The bar in the drawer needs the same per-market numbers. A single global threshold disagrees with your actual rates in every market but one.
The mechanics and copy are covered in the Shopify cart drawer free shipping bar guide; the multi-currency addition is that every number in it needs a per-market variant.
5. Decimal and separator mangling
Cause: JavaScript that parses a formatted string back into a number. parseFloat("1.234,56") returns 1.234. That is the bug behind prices 100× too small after a quantity change, and it only appears in comma-decimal markets — which is why it survives testing from a US desk.
Fix: never round-trip through a formatted string. Keep subunit integers in data attributes (data-cart-total="5427"), do the arithmetic on those, format once with a money filter. Currencies without subunits, such as JPY and KRW, are the other half of the trap — a hardcoded "divide by 100" turns a ¥5,427 cart into ¥54.
Discounts, BOGO and Free Gifts Across Markets
Percentage discounts scale by definition — 20% off is 20% off in every currency. Fixed-amount discounts do not. A "$10 off" code in a EUR market is a different real discount every week as the rate moves, and in a weak-currency market it can exceed the cart value.
Since May 7, 2026, Shopify lets you assign a discount to specific markets natively, including Buy X Get Y. That changes the recommended pattern:
- Fixed-amount: one per market at Shopify admin → Discounts → Create discount → Amount off products, restricted to that market, with a round local value — €10, not the conversion of $10.
- Percentage: one discount, all markets. No duplication.
- Buy X Get Y: quantity-based, so it travels across currencies cleanly. Assign per market only when the gift product differs by region — usually a shipping restriction rather than a pricing decision.
Two limits. Shopify caps active automatic discounts at 25 per store, including Functions — duplicate every offer across eight markets and you hit that ceiling fast. And discounts combine across the product, order and shipping classes only when combinations are enabled on each.
Free gifts need the same treatment: a gift unlocked at "$100" needs a per-market threshold, and the gift may be un-shippable to some destinations. Our guide to a Shopify free gift by country covers targeting; the cart drawer discount code guide has the in-drawer validation flow.
Duties, Import Taxes and What the Drawer Should Say
The rules changed materially and a lot of published advice is now stale. Per Shopify's duties documentation, the US de minimis exemption ended on August 29, 2025 — duties and import taxes now apply to all US imports regardless of value. The EU still waives duty on goods under €150, with low-value VAT handled separately.
Shopify can calculate and collect duties at checkout, but that requires HS codes and country of origin on your products, and DDP labels are supported only through specific carriers. Collected duties are money you hold to pay the carrier invoice later.
For the drawer: duties depend on the shipping address, so they are calculated at checkout, not in the cart. The right pattern is a one-line note near the checkout button — shown only to shoppers outside your fulfilment region — saying whether duties are collected at checkout or billed on delivery. Silence produces refused deliveries; a fabricated estimate produces disputes.
Theme Drawer vs App Drawer for International Stores
| Area | Theme drawer (Dawn/Horizon) | Cart drawer app |
|---|---|---|
| Line items and subtotal in local currency | Yes — Liquid money filters, nothing to configure | Yes, if the app re-renders sections rather than patching the DOM |
| Currency selector in the drawer | Move the existing snippet (code edit) | Usually a toggle, no theme edit |
| Free shipping bar and per-market thresholds | You build it, with custom Liquid branching | Included, but per-market support varies — ask before installing |
| Upsell and free-gift pricing | Not included | Included; prices come from the market's price list |
| Ongoing maintenance | You own every theme update | Vendor owns the code; you own the performance cost |
The honest split: if you sell in two or three markets and only need correct line items, Dawn's drawer plus a relocated country selector is enough and costs nothing. The moment you want market-aware shipping bars, gift thresholds or upsells, you are either writing that Liquid yourself or installing an app.
Oxify Cart Drawer & Upsell lists multi-currency and multi-language among its features, with an interface available in eight languages (English, Spanish, French, German, Italian, Japanese, Brazilian Portuguese and Danish). As with any app here, verify the specific behaviour you need — particularly per-market offer thresholds — on a development store first. Our roundup of the best Shopify cart drawer apps covers the field, and whether a cart drawer app slows your store covers the performance trade-off a second currency script makes worse.
Testing Checklist Before You Ship
Desk-testing from one country will not catch these bugs. Work through this list per market.
- Switch markets with the selector, not a VPN. The localization form is the deterministic path; geolocation is not. Confirm the page reloads and header prices change.
- Preview each market in the theme editor. Its market/country switcher renders the storefront as a given market without leaving the admin.
- Add to cart and open the drawer. Line items, subtotal and shipping bar should all be in local currency with the correct symbol and separator.
- Change quantity twice. This is where DOM-patching drawers break. Watch the subtotal, not just the line item.
- Check a comma-decimal market (Germany, France) for 100× errors, and a zero-decimal currency (JPY) for phantom decimals.
- Apply a discount code. Confirm it is valid in that market and the reduction is a sensible local amount.
- Cross the free-shipping threshold. The bar should complete at the same number your shipping rate actually uses.
- Proceed to checkout. The checkout currency must match the drawer exactly. Any mismatch is a stop-ship.
- Place a test order. Enable Shopify's test payment gateway under Settings → Payments (Shopify Payments test mode, or the Bogus Gateway for third-party setups), order in a non-home market, and confirm the order record shows the presentment and payout currencies correctly.
Run this for at least three markets — one dollar-symbol, one comma-decimal, one you actually get volume from.
Currency Is Half the Job — Language Is the Other Half
A German shopper who sees €54.00 next to an English "Add 1 more for free shipping" still reads the drawer as half-finished. Market and language are separate axes — one market can offer several languages via localization.available_languages — so set them independently. The multi-language cart drawer guide covers translation; if you are new to the component, start with what a Shopify cart drawer is.
Running a Global Drawer Without Building It Yourself
If you would rather configure market-aware offers than maintain Liquid branching through every theme update, Oxify Cart Drawer & Upsell is a Built for Shopify app rated 4.9 from 37 reviews, listing multi-currency and multi-language among its features.
Pricing starts at $9.99/month and scales by monthly order volume rather than by feature, so nothing is gated behind a higher tier. The 14-day free trial is long enough to run the testing checklist above across your real markets first. Details on the cart drawer upsell page.
Frequently Asked Questions
Does the Shopify cart drawer support multiple currencies?
cart.currency returns the customer's presentment currency when the store sells in multiple currencies, and Liquid's money filters format it. A wrong currency is a re-render bug, not a missing feature.Why is my cart drawer showing the wrong currency?
/cart/add.js JSON instead of re-rendered sections; hardcoded /cart paths bypass the market's subfolder; a currency-converter app rewrites prices after the drawer redraws; thresholds are stored in base currency; or JavaScript mangles comma decimals. Fix the re-render first — it resolves most cases.Do I need Shopify Payments for multi-currency?
Can I put a currency selector inside the cart drawer?
snippets/country-localization.liquid inside a {% form 'localization' %} block; Horizon ships snippets/localization-form.liquid. Render it inside your cart drawer section with a unique localPosition or form_id. Submitting reloads the page in the new market, so the drawer closes — correct, because the cart is re-costed.Do discounts work across currencies?
How do I set free shipping thresholds per country?
What about duties and import taxes?
Does a currency app slow down the cart drawer?
The Short Version
Get the market layer right first: Shopify Payments, local currency per market, rounding on. Make sure the drawer never formats money in JavaScript — re-render sections and let Liquid do it. Then give every threshold, gift unlock and fixed-amount discount a per-market value, and finish with a real test order. Currency bugs are cheap to fix and expensive to leave: they surface at the exact moment a shopper is deciding to trust you.