FX & Analytics Currency
Roll multi-currency revenue into one reporting currency for analytics, using a daily FX rate snapshotted onto each order, without ever changing what a customer is charged.
A store that sells into several regions takes money in several currencies. FX & analytics currency is how the dashboard rolls that mixed revenue into numbers a merchant can read, either kept split per currency, or converted into a single reporting currency using a daily exchange rate.
FX here is analytics-only. Conversion never changes what a customer is charged, because the buyer always pays the order's own currency. FX only affects how revenue is displayed in reports.
The two modes
A store's analyticsCurrencyMode decides how multi-currency revenue is summed:
NATIVE(default): revenue is grouped by currency, with no conversion. A report shows one row per currency (€4,200 / $3,100 / ₪9,000). Nothing is added across currencies, so there's no FX error to introduce. This is the existing behavior for every store that never touches the setting.AGGREGATE: every order is converted into the store'sreportingCurrencyso the dashboard can show a single total. The reporting currency is only meaningful in this mode. When it is null/unset, an order is converted into the default region's currency (falling back to the store currency), while the dashboard labels the total with the store currency until a reporting currency is chosen.
The merchant chooses the mode (and, in AGGREGATE, the reporting currency and FX
source) from the toolbar in the header of the Revenue card on the Dashboard
(View: Per currency or Single total; Reporting currency; FX source).
There is no "Analytics Display" page under Settings. The same fields are
analyticsCurrencyMode, reportingCurrency and fxProvider on the store settings
(PATCH store settings).
Scope of the toggle. It drives the by-currency strip under the Revenue chart and
GET /api/analytics/revenue-by-currency(mode,native[],aggregate.total), and it decides what the Dashboard's headline cards add up.
NATIVE: the headline Revenue, Profit and Avg. Order Value cards, their change percentages and the daily chart sum each order's owntotalAmountas-is, with no conversion, and label the result with the store currency. A store that sells in more than one currency should read the by-currency strip, not those cards.AGGREGATE: the same cards, change percentages, the 30-day sparkline and the daily chart sum each order'sreportingAmount(its FX snapshot), falling back tototalAmountfor an order that has none, and returncurrencyequal to the store'sreportingCurrency. This coversGET /api/analytics/dashboard,GET /api/analytics/summaryandGET /api/analytics/sales-comparison. A partial refund comes off at its own order's rate. Revenue then ties out withaggregate.totalby construction.- Profit in
AGGREGATE: cost prices, and thecostfrozen onto an order line, are in the store currency. WhenreportingCurrencyequals the store currency the cost needs no conversion. Otherwise it is converted with the order's own snapshot rate, and that rate is only the right pair for an order placed in the store currency. If any order in the window cannot be converted that way,profit.totalProfit,totalCOGSandprofitMarginarenullwithunavailableReason: "mixed_currencies", the sales comparison'sprofitandchange.profitPercentarenull, and the chart carries no profit line. The dashboard shows "Not available for mixed currencies".- Same rule everywhere:
GET /api/analytics/aov,GET /api/analytics/revenue-timeseriesand thetotalAmountofGET /api/analytics/orders-stats(and of the dashboard'sordersStats) follow it too. InAGGREGATEthey sumreportingAmount ?? totalAmountand return the reporting currency, inNATIVEthey add raw totals as before.
Daily FX rates
Conversion uses an FxRate table with one row per
(store, fromCurrency, toCurrency, effectiveDate), where effectiveDate is
date-only. A daily cron fetches rates from the configured fxProvider and
writes a fresh row per pair:
ECB: the European Central Bank reference rates (free; the default).OPENEXCHANGE: Open Exchange Rates (provider key required).MANUAL: no cron; the merchant enters rates by hand. Manual rates can't be dated in the future and must be greater than zero. There is no dashboard screen for entering them: they are posted to the dashboard API (POST /api/fx-rates, needs the manage store settings permission;GET /api/fx-rateslists the history).
The rate used to convert an order is the newest row on or before the order's
date (from → to as of date). Same-currency conversions are always 1. If no
rate row exists for a pair, the conversion resolves to null: the order is never blocked,
and its reportingAmount stays empty. In AGGREGATE mode such an order is added to the
aggregate.total at its own totalAmount, unconverted, because the report adds
reportingAmount where present and totalAmount for every order where it is null. So a
foreign-currency order that had no rate on the day it was placed inflates the single total
by its face value in another currency. The same holds for the headline cards, their
change percentages and the chart in AGGREGATE mode, which read the same fallback.
Keep a rate row for every currency pair you sell in
(use the MANUAL provider if your provider does not carry a currency).
Per-order snapshot
FX is captured once, at order creation, and frozen onto the order, so later rate changes never rewrite historical revenue. Each order stores three fields:
reportingCurrency: the currency the order was converted into. It is set on every order whose currency differs from the reporting currency, in either mode and even when no rate was available (the snapshot does not readanalyticsCurrencyMode). It is null only when the order is already in the reporting currency.fxRateToReporting: the exact rate applied (order currency → reporting currency), or null.reportingAmount:totalAmount × fxRateToReporting, the order's value in the reporting currency, or null.
Because the snapshot lives on the order, an AGGREGATE report is a plain sum of
reportingAmount (plus totalAmount for the orders where it is null, see above), with no rate lookups at read time, and the figures are reproducible
forever regardless of how today's rate moves.
The order's own
totalAmount/currencyare untouched. That is what the customer paid and what every refund, payout, and invoice uses.reportingAmountsits beside it purely for analytics.
See also Regions for the currencies these orders arrive in.
Tax on refunds
How much of each refund is tax, how it is tied to the order's original tax, and where to read it from the API, the dashboard and the export.
Region-Restricted Coupons
Limit a coupon to specific regions. An empty region list applies everywhere, and a non-empty list redeems only for buyers whose checkout region is in it.