CurrencyCraft looks at how your store is set up, then either steps back and lets Shopify Markets do the pricing, or converts the prices your shoppers see and tells them plainly what currency they will be charged in.
Display-only mode: the switcher, the converted price, and the notice that tells the shopper which currency the charge will actually be in. The illustration shows a storefront header with a currency switcher set to INR, a product priced 10,636.99 rupees, and a dismissible notice reading "You will be charged in USD at checkout". Beside it are the four steps behind that figure: a base price of 128.00 US dollars, an example rate of 1 US dollar to 83.10 rupees, charm99 rounding, and the displayed result of 10,636.99 rupees. The rate is an example, not a live quote.
The problem
A shopper in Toronto lands on a store priced in US dollars. The number on the page is not the number they think in. To work out what the product actually costs them they have to guess a rate, or open a converter in another tab, or give up. Most people pick the third option. The price was never wrong, it was just unreadable.
The fix is to show the price in the currency the shopper thinks in. How that should be done depends entirely on whether your store can actually take payment in that currency, which is where most currency apps get it wrong.
The two modes
On install, and again on any admin page load while the store is still unclassified, CurrencyCraft reads two things from the Admin API: whether Shopify Payments is activated, and whether you have a Market with local currencies switched on. That answer decides the mode. You can override it by hand at any time, and if the signals are ambiguous the app says so rather than guessing.
Mode A: Shopify Markets does the pricing
Triggered when Shopify Payments is activated and a Market has local currencies enabled. Shopify has already localised the price and will take payment in the shopper's currency at checkout, so CurrencyCraft does not convert anything. It refuses on purpose: the price on the page is already a converted price, and multiplying it by an exchange rate a second time gives a number that matches neither Shopify nor reality. CurrencyCraft hides its own picker in Mode A. Use your theme's built-in country selector to change Shopify's native country context. CurrencyCraft does not change Markets prices or checkout currency. Its rounding, rates and Pro conversion rules apply only in Mode B.
Mode B: display-only conversion
Triggered when the store has no Shopify Payments account, so Shopify cannot charge in a local currency. CurrencyCraft converts the prices shown on the storefront and leaves the cart and checkout totals untouched, because they have to settle in your base currency. Rather than hide that, the app shows the shopper a notice: the seeded wording names your store currency, so a store based in US dollars shows "You will be charged in USD at checkout". You can reword it. While the shopper is browsing it is dismissible, and that one dismissal settles the whole store rather than the currency they happened to be in. On the cart it is always shown at full size, it names the exact base-currency total, and it has no close button. The rule that checkout never leaves the base currency is enforced by a test that has to pass on every build.
How a price actually gets converted
This is the Mode B path. In Mode A the app stops after the first step and leaves the price exactly as Shopify rendered it.
1. Detection. The storefront script asks the app which currency this shopper should see. A merchant rule that forces a currency wins first, then the shopper's own saved choice, then the currency implied by the country Shopify resolved for them, then your base currency. Nothing is ever left blank.
2. Live rate. The rate comes from the rates engine. Fiat rates are fetched from OpenExchangeRates, with Frankfurter (the European Central Bank feed) behind it. Crypto rates come from Coinbase and precious metals (XAU, XAG, XPT, XPD) from gold-api.com. Crypto and metals are quoted against USD, so they are triangulated onto your base currency using the fiat provider's own USD leg.
3. Your adjustment. A manual rate you typed in for that currency beats everything. If there is no manual rate, your per-currency markup percentage is applied to the provider rate. If neither is set, the provider rate is used as it came in. A targeted rate override rule can take precedence over both.
4. Rounding. Each currency gets its own strategy: nearest, up, down, or charm endings at .99, .95 or .00. Currencies with no minor unit (JPY) and three-decimal currencies (KWD) are handled correctly rather than forced into two decimals.
5. Display. The formatted result replaces the number on screen. Prices are hidden for a moment before paint and revealed together, so nothing jumps around as the page settles. A mutation observer catches prices added later by the theme, batched into animation frames.
The cart and the checkout are never rewritten. The conversion is a display layer over your theme's markup, and the money Shopify charges is the money Shopify always would have charged.
When a rate provider goes down
Providers fail. The interesting question is what your storefront does in the ninety seconds before anyone notices.
Failover. The engine walks the provider chain in order and takes the first usable answer. A response that parses but contains nothing usable counts as a failure, not as an empty price board.
Circuit breaker. Each provider has its own breaker. Five consecutive failures, or more than half of at least five attempts inside a sixty second window, opens it. While open, that provider is skipped outright. After a sixty second cooldown one probe request is allowed through, and a single success closes it again.
Cached and stale-but-valid. Every successful fetch is cached, and concurrent misses for the same base are coalesced into one upstream call. If every provider in the chain is down, the last good bundle is served and flagged as stale rather than dropped.
Never a broken number. Zero, null and non-numeric entries are stripped out of a rate table before it can reach the cache or the storefront, and they are dropped rather than coerced to zero. If there is genuinely no rate for a currency, the price falls back to the base-currency figure exactly as your theme rendered it. A price never displays as 0, as NaN, or as a blank.
A slow app never blocks the page. If the config request is slow or fails, the script reveals the base prices after three seconds and carries on.
Targeting, if you need it
In Mode B, Pro conversion rules let you target by market, country, currency, or globally, and they can force a currency, make a currency selectable, override a rate, change rounding, or hide the switcher. Resolution is deterministic: market beats country beats currency beats global, then the higher priority number, then the lowest rule id. The admin has a simulator that runs the same rule engine your storefront is answered with, so you can see what a visitor in a given country and market will get before you publish anything. Rules and the simulator are on the Pro plan.
Live simulatorJapanUnited StatesAPAC marketReset
Country JP · currency any · market any
Currency shownJPY (forced by a rule, from Force a currency for country JP, priority 10)
Price shown¥19,800, converted from $128.00
Rate used154.6900 JPY per 1 USD (the live provider rate)
Roundingnearest (your JPY rounding rule)
Switchervisible (no rule changed it)
Currencies this visitor can chooseUSD, EUR, GBP, JPY
The simulator resolves a visitor through the same engine that answers your storefront, and names the rule that won each field, so you can read the outcome before you publish it. Rules and the simulator are part of the Pro plan. The rate above is an example, not a live quote.
What ships to your storefront
A theme app extension: one app embed plus a switcher block you can place where you want it. The script and its stylesheet together stay inside a hard 15 KB gzipped budget, load asynchronously, and are written to reserve space so the layout does not shift when prices are swapped. Storefront analytics are disabled by default at launch.
USD▾DropdownA real native select, so the phone wheel picker and keyboard navigation come for free.
EUR€▾PillA rounded button opening a listbox. Code, symbol and flag are each optional.
JPY▾MinimalThe same control with the chrome removed, for a dense header.
USDEURGBPInlineEvery currency on screen at once, as a radio group. Best with three or four.
Four presets for Mode B display conversion, set in the app. The switcher renders once at least two currencies are enabled, because a switcher with one option is not a switcher.
Two steps the app cannot do for you
Wrap your money format, in Mode B only
Under Settings, General, Currency formatting, your price has to be wrapped in a cc-money span, in both HTML fields. Shopify has no Admin API that writes those fields, so no app can do it for you. It is a paste that takes about a minute, the setup guide gives you the exact strings, and the app then reads your live formats back from Shopify and reports each field as done or not. In Mode A the wrapper is not merely unnecessary, it would set up a double conversion, so you are never asked for it there.
Switch on the app embed, in Mode B only
The theme app extension is enabled in the theme editor under App embeds, and the switcher block is placed in a section separately. Installing the app does neither. CurrencyCraft does not request the read_themes scope, so it cannot check this for you or turn it on: the setup guide asks you to confirm it. Skipping it is the most common reason a storefront shows no change at all.
Both are Mode B setup steps. For Mode A, use your theme's native country selector. The setup guide gives you the exact strings and where they go, and the same guide lives inside the app against your live settings.
Free covers up to 5 fiat currencies. Growth is $4.99/month and Pro is $9.99/month, with a 30-day trial on a new paid subscription after Shopify approval.