Title: MHM Currency Switcher
Author: MHM Development Team
Published: <strong>August 30, 2026</strong>
Last modified: August 30, 2026

---

Search plugins

![](https://ps.w.org/mhm-currency-switcher/assets/banner-772x250.png?rev=3672756)

![](https://ps.w.org/mhm-currency-switcher/assets/icon-256x256.png?rev=3672756)

# MHM Currency Switcher

 By [MHM Development Team](https://profiles.wordpress.org/maxhandmade/)

[Download](https://downloads.wordpress.org/plugin/mhm-currency-switcher.2.1.0.zip)

 * [Details](https://wordpress.org/plugins/mhm-currency-switcher/#description)
 * [Reviews](https://wordpress.org/plugins/mhm-currency-switcher/#reviews)
 *  [Installation](https://wordpress.org/plugins/mhm-currency-switcher/#installation)
 * [Development](https://wordpress.org/plugins/mhm-currency-switcher/#developers)

 [Support](https://wordpress.org/support/plugin/mhm-currency-switcher/)

## Description

MHM Currency Switcher adds multi-currency support to your WooCommerce store. Customers
can browse products, add items to their cart, and complete checkout in their preferred
currency with real-time exchange rates.

**Key Features**

 * Add multiple currencies with real-time exchange rates
 * Cache compatibility mode — works behind a page cache without serving one visitor’s
   currency to everybody
 * Currency switcher via shortcode — usable in a text widget, nav menu, or Elementor
 * Product page price display widget with country flags
 * Navigation menu integration — add switcher to any WordPress menu
 * Automatic exchange rate fetching (ExchangeRate-API)
 * Fee & rounding configuration per currency
 * Cookie-based currency persistence
 * WooCommerce HPOS compatible
 * Elementor widgets included
 * Every currency WooCommerce offers
 * Scheduled automatic exchange rate updates
 * Geolocation-based currency detection
 * Fixed prices per product
 * “How to use” tab in the settings screen, naming every way the switcher can be
   placed

**Cache compatibility mode**

A page cache stores the HTML your server produced for whoever asked first. When

prices are converted on the server, that means the first visitor’s currency is what
every later visitor is served. Cache compatibility mode, which is on by default,
avoids this: anonymous shop, archive and product pages are rendered in your base
currency, so the same cached page is correct for everyone, and the browser converts
the prices it can see afterwards through a REST request to this plugin.

The split is deliberate. Only _displayed_ prices are converted in the browser.
 
Cart, checkout, order totals, order emails and the WooCommerce REST API are always
calculated on the server in the currency the customer actually chose, so the amount
charged cannot be altered from the browser.

You can switch the mode off under **WooCommerce > MHM Currency >
 Advanced
, in 
which case prices are converted on the server as they were before
 this feature 
existed. Read “Known limits” below before deciding either way — both settings have
consequences, and they are different ones.

### Known limits

These are consequences of how cache compatibility mode works, not defects. They

are listed so you can decide with your eyes open.

#### Turning the mode off does not restore the exact 1.0.0 behaviour

Three fixes sit above the setting and stay in place whether it is on or off:
 prices
are no longer converted on admin screens or in admin AJAX (which used to write a
converted price into an order line item), `wc/v3` REST reads are pinned to the base
currency, and scheduled tasks and WP-CLI no longer convert. Switching the mode off
restores the 1.0.0 _display_ behaviour — prices converted on the server — and nothing
else.

#### Search engines and crawlers see your base prices

With the mode on, the page a crawler fetches has not been through the browser,
 
so it carries base-currency prices, and so does the machine-readable product data
in it. See the structured data question above; the mismatch is deliberate.

#### Converted prices replace the base ones a moment after the page appears

The page arrives with your base-currency prices already on screen — nothing is
 
hidden waiting for JavaScript — and the browser swaps in the converted ones as soon
as its request comes back, each price fading over 200ms as it changes. On a slow
connection the base price is readable for longer before the swap. Visitors who have
asked their system for reduced motion get the swap without the fade.

#### With JavaScript disabled, or the endpoint unreachable, base prices stay

Nothing breaks and no error is shown to the visitor — the page simply keeps the

base-currency prices it was rendered with, and the reason is written to the browser
console. Cart and checkout are unaffected, because they never depended on the browser
in the first place.

#### Variable products make one extra request, and some swatch plugins lose the price

On a cacheable page the plugin forces WooCommerce to fetch variation prices over

AJAX, because the variations JSON WooCommerce would otherwise embed in the page 
carries base-currency prices that its own scripts write straight into the page. 
The cost is one request when a visitor picks a variation, and that data-product_variations
is `false`: third-party colour or size swatch plugins that read prices out of that
JSON instead of asking WooCommerce may stop showing a price. If you use such a plugin,
check a variable product before going live.

#### The mini-cart relies on WooCommerce cart fragments

A mini-cart is rendered on every page, so it cannot be classified per request.
 
It is rendered in the base currency and then corrected by WooCommerce’s own cart
fragment refresh, which is a server-side conversion. If cart fragments are disabled
on your site — some themes and optimisation plugins dequeue them — the cached mini-
cart total stays in the base currency while the rest of the page converts. The plugin
watches for this and says so in the admin when it happens; a site with no mini-cart
at all is never warned about it.

#### A cart or checkout on a page WooCommerce does not know about must be excluded from your cache

Page caches exclude cart and checkout automatically because they recognise the
 
pages WooCommerce assigned. If you have put a cart or checkout shortcode or block
on some other page, exclude that page yourself. Two things go wrong otherwise: the
conversion decision flips to “convert” partway through the render, so the rest of
the page is printed already converted and without the markers the browser looks 
for, and blocks-based cart and checkout embed their amounts in the page as JSON 
while rendering. Either way the first visitor’s currency is what the cache then 
hands to everyone. Cart contents are personal anyway; such a page should not be 
cached.

#### `?currency=` multiplies your cache entries

A currency can be requested in the URL, and a cache treats every distinct URL as

a separate entry, so linking to `?currency=EUR` and `?currency=GBP` stores the same
page more than once. The switcher itself does not produce these URLs — it writes
a cookie and leaves the address alone. On a cached page it converts the prices where
they stand; on the cart page, for a logged-in visitor, or with cache compatibility
switched off, it reloads instead. Either way the URL is the one the visitor was 
already on, so no extra cache entry is created.

A `?currency=` link also applies to that page view only: it deliberately sets no

cookie, so the next page the visitor opens is back in your base currency unless 
they use the switcher. That is not an oversight — a link that silently pinned a 
currency could show one currency in the catalogue while the cart, which reads the
cookie, charged another. If you want a campaign link that sticks, send visitors 
to a page carrying the switcher rather than relying on the parameter.

#### WooCommerce Analytics adds different currencies together

An order is stored in the currency the customer paid in, and WooCommerce
 Analytics
reports every order’s figures in your store currency without converting them back.
A 4.38 USD order is counted as 4.38 in your base currency, so once you take orders
in more than one currency the revenue figures in Analytics, and the totals in the
customer panel on the order screen, are sums of unlike amounts. The orders themselves
are correct — each one keeps its own currency, total and the exchange rate it was
placed at, and this plugin stores that rate on the order. It is the aggregate reports
that cannot be read as money. Nothing in this plugin can fix that from the outside;
if you need accurate multi-currency reporting, export the orders and convert them
using the rate recorded on each one.

#### Logged-in visitors are converted on the server

Logged-in visitors take the server-side path, which is correct as long as your
 
cache does what nearly all of them do and never serves cached pages to logged-in
users. An edge cache or CDN configured to cache without looking at cookies is the
exception, and there a logged-in visitor’s converted page can be stored and served
on. If you cache at the edge, confirm it varies on the login cookie.

### External services

This plugin connects to two third-party services to keep currency conversion
 rates
up to date. What each one is sent, and when, is described separately below because
the two are not the same. Both requests are made with PHP’s WP_Http transport, which
in WordPress’s default configuration sends a User-Agent header of the form `WordPress/{
version}; {your site's URL}` — so the site’s own address leaves with every request
to either service, not just the data described below. That header is filterable (`
http_headers_useragent`, `http_request_args`), so a site that has changed it will
send something different.

**ExchangeRate-API**

What it is: a commercial exchange-rate API, used as the primary source of
 exchange
rates.

What is sent, and when: the three-letter base currency code you have
 configured(
for example `USD`), sent as part of the request URL — https://api.exchangerate-api.
com/v4/latest/{BASE_CURRENCY} — when you press “Sync rates” in the admin panel, 
when you run `wp mhmcs rates-sync` from the command line, and on the schedule you
configure under automatic rate updates (hourly, twice daily, or daily). No other
data from your site is included.

Terms of service and privacy policy: https://www.exchangerate-api.com/terms
 (ExchangeRate-
API publishes its privacy policy inside that same page rather than on a separate
one.)

**European Central Bank (ECB) daily reference rates**

What it is: the ECB’s public daily reference-rate feed, used as the fallback
 when
ExchangeRate-API cannot be reached.

What is sent, and when: nothing beyond the User-Agent described above. The
 feed
is a fixed, parameter-free address — https://www.ecb.europa.eu/stats/eurofxref/eurofxref-
daily.xml — so no currency code or other value is sent to the ECB; the same document
is returned to every requester. It is only requested when ExchangeRate-API’s request
has failed. The feed is EUR-based and covers roughly thirty currencies rather than
the hundreds ExchangeRate-API carries. If your base currency is outside that set,
this source returns nothing at all and your existing rates are left unchanged until
the next attempt; if only one of your configured target currencies is outside that
set, the other target currencies still update and the unsupported one is simply 
left without a rate from this source.

The ECB does not publish a document titled “Terms of Service.” Its terms of
 use
are stated on its Disclaimer & Copyright page, which is the closest equivalent and
is linked below.

Disclaimer & Copyright (terms of use): https://www.ecb.europa.eu/services/using-
our-site/disclaimer/html/index.en.html
 Privacy statement: https://www.ecb.europa.
eu/services/data-protection/privacy-statements/html/ecb.privacy_statement_website.
en.html

The ECB’s reference-rates page separately states that using these rates for
 transaction
purposes is strongly discouraged: https://www.ecb.europa.eu/stats/policy_and_exchange_rates/
euro_reference_exchange_rates/html/index.en.html That page, not the two links above,
is the source of that caution. This plugin uses the feed to convert prices for display
and checkout, which is the kind of transactional use that notice is about; if that
matters for your shop, review that page before relying on this fallback.

**Redirecting a source**

If your network blocks the ECB feed, the `mhmcs_fallback_rates_url` filter
 receives
the URL, the base currency code and which source is being filtered — always `'ecb'`,
since ECB is now the only fallback — so the request can be pointed elsewhere.

**Visitor geolocation (through WooCommerce)**

When “Enable geolocation-based currency detection” is switched on, the plugin
 asks
WooCommerce which country a visitor is in, using WooCommerce’s own WC_Geolocation
API. Depending on how your site is configured, WooCommerce answers that either from
a local MaxMind database or by contacting the remote geolocation service it is configured
to use — the request and the service are WooCommerce’s, not this plugin’s, and this
plugin sends nothing itself. The setting is off unless you turn it on.

WooCommerce geolocation documentation:
 https://woocommerce.com/document/maxmind-
geolocation-integration/

### Source code

The settings screen is a React application, and what ships inside the plugin is

the compiled bundle at `admin-app/build/index.js`. The readable source it is built
from is not in the package, so here is where to find it and how to reproduce the
build.

Full source, including the unminified JavaScript:
 https://github.com/MaxHandMade/
mhm-currency-switcher

The source of the bundle is `admin-app/src/`. It is compiled with WordPress’s
 own
build tooling, @wordpress/scripts, and nothing else:

    ```
    npm install
    npm run build
    ```

That writes `admin-app/build/index.js` together with the `index.asset.php`
 dependency
map the plugin reads when enqueuing the script. No other build step, minifier or
bundler is involved, and no code is generated at install time or at runtime.

## Screenshots

[⌊A shop priced in US dollars, as a visitor sees it after choosing Turkish
lira.
The page itself was served in the shop's base currency; the prices
were converted
afterwards.⌉⌊A shop priced in US dollars, as a visitor sees it after choosing Turkish

lira. The page itself was served in the shop's base currency; the prices
were converted
afterwards.⌉[

A shop priced in US dollars, as a visitor sees it after choosing Turkish lira. The
page itself was served in the shop’s base currency; the prices were converted afterwards.

[⌊The switcher added to a site's navigation menu, with its list open.⌉⌊The switcher
added to a site's navigation menu, with its list open.⌉[

The switcher added to a site’s navigation menu, with its list open.

[⌊Manage Currencies — each currency has its own rate, fee and rounding rules,
and
every row shows the converted price a customer would see.⌉⌊Manage Currencies — each
currency has its own rate, fee and rounding rules,
and every row shows the converted
price a customer would see.⌉[

Manage Currencies — each currency has its own rate, fee and rounding rules, and 
every row shows the converted price a customer would see.

[⌊Display Options — a live preview of the switcher, what it shows, how large
it 
is, and whether product pages carry a multi-currency price list.⌉⌊Display Options—
a live preview of the switcher, what it shows, how large
it is, and whether product
pages carry a multi-currency price list.⌉[

Display Options — a live preview of the switcher, what it shows, how large it is,
and whether product pages carry a multi-currency price list.

[⌊Advanced — geolocation, the automatic rate-update interval, cache
compatibility
mode, and whether removing the plugin deletes its data.⌉⌊Advanced — geolocation,
the automatic rate-update interval, cache
compatibility mode, and whether removing
the plugin deletes its data.⌉[

Advanced — geolocation, the automatic rate-update interval, cache compatibility 
mode, and whether removing the plugin deletes its data.

[⌊How to use — every way the switcher can be placed, including the navigation
menu
item, with copyable code and the price-list shortcode's attributes.⌉⌊How to use —
every way the switcher can be placed, including the navigation
menu item, with copyable
code and the price-list shortcode's attributes.⌉[

How to use — every way the switcher can be placed, including the navigation menu
item, with copyable code and the price-list shortcode’s attributes.

## Installation

 1. Upload the `mhm-currency-switcher` folder to the `/wp-content/plugins/` directory,
    or install directly through the WordPress plugin screen.
 2. Activate the plugin through the ‘Plugins’ screen in WordPress.
 3. Make sure WooCommerce is installed and activated.
 4. Go to **WooCommerce > MHM Currency** to configure your currencies and exchange 
    rates.

## FAQ

### What are the shortcodes?

[mhmcs_currency_switcher] renders the currency dropdown. It accepts one
 attribute,`
size`, which may be `small`, `medium` or `large`; leave it out to use the size saved
in Display Options.

    ```
    [mhmcs_currency_prices] renders the same product price in several currencies.
    ```

Its attributes are all optional:

 * `currencies` — comma-separated codes, e.g. `currencies="USD,EUR"`. Without
    it,
   the currencies chosen in Display Options are used. Codes you have not configured
   as currencies are ignored.
 * `product_id` — price a specific product instead of the one being viewed.
 * `price` — price a specific amount instead of a product’s.
 * `show_flags` — `true` or `false`, overriding the saved Display Options
    setting.

Both are also available as Elementor widgets.

### How many currencies can I add?

As many as your shop needs — every currency WooCommerce offers can be enabled,
 
and nothing is held back for a paid version. The REST API refuses a request carrying
more than 500 currency rows; that is a guard against oversized payloads and is well
above the number of codes WooCommerce itself offers, so the panel cannot reach it.

### How are exchange rates fetched?

Exchange rates are fetched from ExchangeRate-API in real time, either on demand 
or on a schedule you configure (hourly, twice daily, or daily) so your rates stay
current without manual intervention.

If that source cannot be reached, the plugin falls back to the European Central 
Bank’s daily reference rate feed before giving up and leaving your existing rates
in place. Both are named, with their terms, under “External services” below. If 
your network blocks the fallback, the `mhmcs_fallback_rates_url` filter can point
it somewhere else.

### Is the plugin compatible with WooCommerce HPOS?

Yes. MHM Currency Switcher fully supports WooCommerce High-Performance Order Storage(
HPOS / Custom Order Tables).

### Can a per-product fixed price be a sale price?

No. A fixed price is stored per product and currency, not per price type, so the
same amount is used for the regular price and the sale price. A product on sale 
in your base currency shows as not on sale in a currency you have given a fixed 
price to. If you need the sale to carry across, leave that currency to the exchange
rate instead of fixing it.

### Is there a limit on how often the conversion endpoint can be called?

Yes. When cache compatibility mode is on, prices on cached pages are converted through
a public REST endpoint, and one address may call it 120 times a minute by default.
Ordinary browsing is nowhere near that — a page makes one request. If your shop 
sits behind a reverse proxy or a CDN that makes every visitor look like the same
address, raise or disable the limit with the `mhmcs_convert_rate_limit` filter. 
Note that the address is read from the proxy headers WooCommerce passes on, which
it trusts unconditionally and which can be forged; the limit bounds accidental hammering
rather than a determined attacker.

### I see a warning that cache compatibility is not being applied. What is it?

Some themes and plugins define WooCommerce’s cart constant on every page, usually
to show a cart total in the header. When that happens the plugin treats every page
as a checkout — the customer’s money is at stake — and converts prices on the server,
which is exactly what cache compatibility mode exists to avoid. Nothing looks wrong
on the site: prices are still correct for whoever loads the page first, and then
a page cache can serve that person’s currency to everyone else. Because there is
no visible symptom, the plugin says so in the admin instead. The notice clears itself
as soon as a front-end page renders normally again.

### Why does the structured data show a different currency from the price on the page?

With cache compatibility on, the page is generated in your base currency and the
browser converts the prices afterwards, so the machine-readable product data a crawler
reads stays in the base currency. This is deliberate and is not corrected: pinning
it the other way would leave the data disagreeing with the page in the one case 
where the two currently agree — with cache compatibility switched off, where the
page and the structured data are both converted on the server.

### Does the WooCommerce REST API return converted prices?

Only when the request asks for a currency: `?currency=EUR` on a `wc/v3` product 
request converts `price`, `regular_price` and `sale_price` and adds a `currency_code`
field. Without the parameter the response is pinned to your base currency, so the
answer never depends on the cookies of whoever is calling. A per-product fixed price
takes precedence over the exchange rate here, exactly as it does on the shop page.

One known limit: the `price_html` field is not pinned in the same way. It cannot
be reached in that state by a normal `wc/v3` client — only by code that dispatches
an internal REST request during a page render — so no integration sees it, but it
is not consistent with the three numeric fields and is recorded here rather than
left unsaid.

## Reviews

There are no reviews for this plugin.

## Contributors & Developers

“MHM Currency Switcher” is open source software. The following people have contributed
to this plugin.

Contributors

 *   [ MHM Development Team ](https://profiles.wordpress.org/maxhandmade/)

[Translate “MHM Currency Switcher” into your language.](https://translate.wordpress.org/projects/wp-plugins/mhm-currency-switcher)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/mhm-currency-switcher/),
check out the [SVN repository](https://plugins.svn.wordpress.org/mhm-currency-switcher/),
or subscribe to the [development log](https://plugins.trac.wordpress.org/log/mhm-currency-switcher/)
by [RSS](https://plugins.trac.wordpress.org/log/mhm-currency-switcher/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

#### 2.1.0

 * BREAKING: switcher CSS classes were renamed from `mhm-cs-*` to `mhmcs-*`.
    Every
   class in the switcher’s CSS and JS — `.mhm-cs-switcher`, .mhm-cs-dropdown, `.
   mhm-cs-size--small`, and the rest — now uses the mhmcs- prefix instead. If you
   have written custom CSS or JavaScript that targets one of the old `mhm-cs-*` 
   classes, it will stop matching after you update: change your selectors to the
   new `mhmcs-` names. No alias is kept for the old classes.
 * BREAKING: the `mhmcs_fallback_rates_url` filter’s `$source` argument is
    now 
   always `ecb`. The exchange-rate fallback no longer has two stages (`currency-
   api` then `frankfurter`); it is now a single request to the European Central 
   Bank’s daily reference feed. Code that checked for $source === ‘currency-api’
   or `'frankfurter'` will no longer see those values.
 * BREAKING: the settings migration for sites still on a pre-1.0.0 install
    was 
   removed, along with its matching legacy-license cleanup and the equivalent old-
   option branches in the uninstall routine. Every version released since 1.0.0 (
   2026-07) is unaffected; only a site that has never updated past the 2026-03 release
   would have skipped a migration it otherwise would have received on activation.
 * Changed: the exchange-rate fallback is now a single European Central Bank
    request
   instead of the two-stage `currency-api.pages.dev` then Frankfurter chain added
   in 2.0.0. Coverage is unchanged — the same roughly thirty currencies — because
   that was already the final stage of the old chain; the extra intermediate stage
   is removed as no longer useful.
 * Added: a snooze option for the two cache-compatibility admin notices.
    Dismissing
   a notice now clears it only for the specific problem currently detected — it 
   comes back automatically if a new, different cache problem appears later, instead
   of going silent for good.
 * Added: the WooCommerce-missing notice and both cache-compatibility
    notices now
   only appear on the screens where they belong, instead of on every admin screen.
   Capability requirements differ by notice: the WooCommerce-missing notice needs`
   activate_plugins` (the capability to act on it — you install a plugin), and the
   two cache-compatibility notices need `manage_woocommerce`.
 * Fixed: every remaining `mhm-cs-` / `mhm_cs_` / `mhm_currency_switcher_`
    legacy
   naming token still in the plugin — across CSS, JavaScript, the admin panel source
   and its compiled build, tests and documentation — has been renamed to the current`
   mhmcs` prefix. This is the last of the prefix cleanup that started with the 2.0.0
   shortcode and option rename.
 * Fixed: the public, unauthenticated `GET /rates` REST endpoint was
    removed. It
   duplicated information already visible in the page itself; the authenticated `/
   rates/sync` and `/rates/preview` endpoints used by the admin panel are unaffected.
 * For developers: `languages/` is no longer included in the release ZIP —
    WordPress.
   org compiles translations from the `.pot` file, which is still kept in the plugin’s
   repository.

#### 2.0.0

 * BREAKING: the two shortcode tags were renamed. `[mhm_currency_switcher]`
    is 
   now `[mhmcs_currency_switcher]`, and `[mhm_currency_prices]` is now [mhmcs_currency_prices].
   The old tags are gone; a page still holding one will show the raw text instead
   of the switcher, so update any page, post or template that uses them. WordPress.
   org’s prefix check splits a prefix at the first underscore, which read the old
   tags as “mhm” — three letters, under the four-letter minimum — and a plugin cannot
   be reviewed under a name the review tool cannot attribute to it. Stored data 
   is untouched: option names, order meta and per-product fixed prices all keep 
   the names they had.
 * Changed: the fallback exchange rate source is served from a different host.
    
   Rates now fall back to `latest.currency-api.pages.dev` instead of the jsDelivr
   CDN. Same upstream project, same payload; only the host changed. WordPress.org’s
   review tool keeps a fixed list of public CDN domains and treats a shipped source
   naming one as an error, whatever the URL fetches. If your store restricts outbound
   requests to an allow-list, permit the new host — or redirect it with the filter
   below.
 * Added: a SECOND fallback rate source, so a blocked network is no longer a
    dead
   end. The host serving the first fallback is unreachable from some national networks—
   Turkey, measured — and not merely by DNS: resolving it over DNS-over-HTTPS and
   connecting straight to the real addresses with the correct SNI still times out,
   while other hosts on the same infrastructure answer normally. A server there 
   cannot route around it by changing resolvers, the old host cannot be restored(
   that is the rule above), and the upstream project documents no third mirror. 
   The chain now ends at Frankfurter, a different provider serving European Central
   Bank reference rates: no API key, commercial use permitted, no attribution required.
   The trade is coverage — around thirty currencies rather than hundreds — which
   is the right trade for a source reached only after two others have failed, and
   the set includes the currencies shops actually price in.
 * Added: a filter, `mhmcs_fallback_rates_url`, which receives the URL, the base
   
   currency code and which source is being filtered (`currency-api` or frankfurter),
   so a store can move one source without moving the others.
 * Added: an About tab, with links to the documentation site, the WordPress.org
   
   support forum and the issue tracker, plus how to reach the developer.
 * Added: the Advanced tab now says WHEN the next automatic rate update is due,
   
   not only how often it repeats. It gives the scheduled time in the store’s own
   timezone and format, how long that is from now, and states plainly that WordPress
   runs scheduled work on the first visit after that time rather than exactly on
   the hour.
 * Fixed: currencies with no minor unit, or with three, were given two decimals.
   
   When a currency carried no explicit decimal setting the fallback was a fixed 
   2 rather than the store’s own configuration, so a shop adding JPY saw 100.00 
   and one adding BHD lost a digit. The table is derived from ICU’s minor-unit data;
   37 of the 163 codes WooCommerce offers are not two-decimal currencies. No `intl`
   extension is required.
 * Fixed: the Advanced tab claimed nothing was scheduled right after it
    scheduled
   something. Switching from “Manual only” to a recurring interval and saving armed
   the event, but the panel kept the schedule it had read when the page loaded and
   showed “Automatic updates are switched on, but no update is scheduled. Re-save
   this setting to schedule one.” Re-saving changed nothing; only reloading the 
   page did. The save response now carries the schedule it armed.
 * Fixed: saving unrelated settings moved the next rate update. The panel sends
   
   the whole settings form, and the scheduler asked whether the interval had been
   submitted rather than whether it had changed — so toggling anything on that screen
   re-armed the event at the current moment, shifting a daily store’s update time
   and making the next visit run a full sync. Saving now reconciles the schedule
   with the stored interval instead: it re-arms on a real change, restores an event
   that has gone missing while the setting still promises one, and clears one left
   standing behind “Manual only”.
 * Fixed: rate-limited responses (HTTP 429) from the public conversion endpoint
   
   carried no cache headers, so a proxy or page cache could store a rejection and
   serve it to callers who were not over the limit.
 * Fixed: the admin styles for the per-currency price fields on the product and
   
   variation screens moved out of the markup and into a stylesheet.
 * Fixed: a translator note on one admin message was placed where the linter
    could
   not see it, so that string had been shipping without its note attached.
 * For developers: the plugin’s source and build steps are now named in
    readme.
   txt, and every URL the About tab shows is a plain link with no tracking parameters.

#### 1.3.1

 * Fixed: a currency with no usable exchange rate could still be used for
    prices,
   and the amount and the currency it was shown in came from different decisions.
   A currency added in the panel starts at a rate of 0 until the first sync, and
   a per-product fixed price was applied under it while the symbol and code fell
   back to the base currency — a foreign amount wearing the base currency’s identity,
   on the catalogue and in the cart and the charge. Such a currency now resolves
   to the base currency everywhere, which is what the panel already said would happen.
 * Fixed: the WooCommerce REST API (wc/v3) applied a per-product fixed price for
   
   that same unusable currency when one was asked for with `?currency=`, while every
   other field in the response, and the currency a client reads it under, stayed
   in the base currency. Feeds, stock syncs and marketplace integrations took that
   price as fact.
 * Fixed: orders placed in the shop’s own currency recorded an exchange rate of
    1. That field is the record of what the customer was charged, so anything
        reconstructing
       it — a report, an accounting export, a refund — had nothing to work from. New
       orders record a rate of 1. Orders already placed keep the value they were saved
       with.
 * Fixed: a save that never reached the database was reported as a success.
    This
   affected the currency list, the settings screen, and all three ways rates are
   synced — the panel button, the hourly schedule and WP-CLI — and in the sync case
   the “last synced” time moved forward regardless, so the panel said the rates 
   were current while it served the old ones. Every one of them now reports the 
   failure, and saving again with nothing changed is still reported as a success.
 * Fixed: upgrading from a pre-0.3.0 version could lose the currency
    configuration.
   The migration copied the old settings across, then deleted the originals and 
   marked itself finished — without checking that the copy had been written. If 
   it had not, there was nothing left and nothing tried again. It now leaves everything
   in place and retries on the next request.
 * Fixed: the rate, fee and rounding fields accepted numbers that cannot be
    stored—
   text, a value too large to represent, or a negative rate — and a single one of
   them discarded every other currency in the same save. They are now corrected 
   and the panel names each correction, as it already did for the decimal count.
 * Fixed: a per-product fixed price entered as a number too large to represent
    
   was stored in a form that reads back as zero, which offered the product for nothing
   in that currency. A negative fixed price was accepted as well. Both are now refused,
   on the product page and the variation rows alike.

#### 1.3.0

 * Added: the settings screen was rebuilt. Currencies are now one table where
    each
   row carries its own rate, fee and rounding controls and shows the price a customer
   would actually see in that currency — computed on the server with the store’s
   own price formatter, not estimated in the browser.
 * Added: a number format editor per currency — symbol, symbol position,
    decimals,
   and the decimal and thousand separators. WooCommerce stores one set of these 
   for the whole shop because it assumes one currency; this plugin shows several.
   Until now the data was stored but there was no field to edit it with, so a currency
   saved with the wrong symbol — which happens when another multi-currency plugin
   is filtering WooCommerce at the time — could not be corrected from the panel 
   at all.
 * Added: a rate freshness indicator, on each currency row and on the Advanced
    
   tab. Where no sync has been recorded it says exactly that — “No sync recorded
   yet” — rather than claiming a sync never ran, so a shop upgrading to this version
   is not told its working rates are missing.
 * Added: an option, off by default, to delete all of the plugin’s data when the
   
   plugin is removed. Left off, your settings and the currency and exchange rate
   recorded on each order survive uninstalling. Those records are the only basis
   for multi-currency sales history and cannot be rebuilt afterwards. On a multisite
   network the switch clears the site the plugin is removed from.
 * Added: the “How to use” tab now documents the navigation menu item, and says
   
   plainly that Appearance  Menus only appears when the active theme supports menus
   or widgets, which most block themes do not.
 * Added: Turkish translations for everything above.
 * Fixed: an exchange rate below 1 could not be typed into the panel. Typing
    0.0211
   left 211 in the field, and 0.05 left 5, without warning. A shop whose base currency
   is weaker than the currencies it sells in has no rate above 1, so the field could
   not take a single realistic value. The rate, fee and rounding amounts were all
   affected, in 1.2.0 as well. All of them now keep what you type.
 * Fixed: the switcher preview on Display Options left out the base currency and
   
   ignored the “show currency symbol” toggle, so it showed a different list from
   the one a visitor gets. It now matches. The product price widget’s currency field,
   separately, shows how many of its five allowed currencies are chosen.
 * Fixed: the product price list’s five-currency limit was enforced only in the
   
   browser. A sixth currency sent to the REST API was stored and then silently dropped
   when the list rendered. The server now applies the same limit and reports it 
   instead of dropping the extra quietly.
 * Fixed: a variable product’s advertised price range could be served from an
    old
   exchange rate. WooCommerce caches that range for up to 30 days, keyed by a hash
   this plugin only put the currency code into — so after a rate update the range
   kept coming from the old rate while every other price on the site used the new
   one. A shopper could read one range and be charged more than its top end. The
   key now includes the rate and rounding the amounts depend on.
 * Fixed: `GET /settings` returned the whole stored option, including keys whose
   
   controls were removed in an earlier release. One of them, `provider_api_key`,
   is a credential you supplied. Saving settings has always dropped those keys, 
   but a shop that had not pressed Save since then still held the value, and the
   read route handed it to anyone with the “manage WooCommerce” capability — which
   includes shop managers, who are not administrators. The read route now filters
   the same list the save route and the uninstaller do.
 * Fixed: the currency picker’s popover did not close on Escape, and closing it
   
   did not return keyboard focus to the button that opened it.
 * Fixed: values typed into the new format fields are corrected rather than
    silently
   accepted — a separator longer than one character, a decimal count outside 0 to
   4, or identical thousand and decimal separators — and the screen names every 
   correction it made instead of changing your input without saying.
 * Changed: the REST API now refuses a save or preview request carrying more than
   
   500 currency rows, rather than accepting a payload of any size. The panel cannot
   produce such a request — WooCommerce offers 163 currency codes in total — so 
   the guard only fires on something that did not come from it.
 * Changed: the settings screen is wider, 1200px rather than 900px. Below that
    
   width the currency table stacks into one card per currency, each field labelled,
   rather than being cut off at the edge.

#### 1.2.0

 * Added: a “How to use” tab in the plugin’s settings screen. The plugin can be
   
   placed in five different ways — two shortcodes, two Elementor widgets and a navigation
   menu item — and none of them were named anywhere in the admin, so after adding
   currencies there was nothing to tell you why the switcher had not appeared on
   your site. The new tab lists every option with copyable code, a table of the 
   price-list shortcode’s attributes, and a note that the Appearance  Menus route
   only exists on classic themes.
 * Fixed: five labels in the currency picker on the settings screen were written
   
   in Turkish, so they stayed Turkish in every other language, English included.
   They are English now, and the Turkish wording moved into the Turkish translation
   file.
 * Fixed: the navigation menu switcher printed its CSS class twice in the menu
    
   item’s class attribute. This was not visible on the storefront, but it was wrong.
 * Fixed: when your browser does not allow a page to write to the clipboard, the
   
   copy buttons on the new tab select the code and tell you to copy it. That message
   named a Windows keystroke; it no longer names a key at all.

#### 1.1.3

 * Fixed: upgrading from a version older than 0.3.0 reset the plugin’s settings,
   
   and on a shop that had configured currencies it lost the currency list entirely.
   The option names changed in 0.3.0 without a migration, and the current names 
   are only written when the plugin is activated — which does not happen during 
   an in-place update. A one-time migration now carries the old settings and any
   configured currencies onto the current names, removes the old rows, and cancels
   the pre-0.3.0 rate-update task so it cannot keep firing. Sites that never configured
   anything simply get the standard defaults: the four currency codes the old installer
   wrote were in a format the plugin could never read, so they were never in use
   and are not carried over.
 * Fixed: the Advanced tab showed “Daily” as the automatic rate-update interval
   
   on sites that had never chosen one, but nothing was scheduled — the setting screen
   and the scheduler disagreed about what an unset interval means. The screen now
   shows “Manual only”, which is what the plugin actually does until you pick an
   interval.
 * Fixed: the multi-currency price display showed prices for currencies you had
   
   not configured, and the figure it showed was the base price wearing the other
   currency’s code and flag. On a shop with nothing configured, the Elementor price
   widget did this out of the box, because its default currency list is USD, EUR,
   GBP. Currencies you have not configured are now left out.
 * Documented the `[mhm_currency_switcher]` and `[mhm_currency_prices]`
    shortcodes
   and their attributes, which this file advertised but never named.
 * Fixed: on a shop running a second currency plugin, adding a currency could
    save
   it with the wrong symbol — every new currency picked up the symbol of whichever
   currency was being displayed, so a US Dollar currency could print your base currency’s
   sign on the storefront. New currencies now take their symbol from WooCommerce’s
   own currency table. Currencies added before this release keep whatever symbol
   was stored; if one of yours shows the wrong sign, remove it and add it again.

#### 1.1.2

 * **Requires WordPress 6.6.** The settings screen never loaded on 6.0 to 6.5:
    
   the admin bundle depends on a script handle WordPress only registers from 6.6,
   and an unregistered dependency makes WordPress drop the script silently. Every
   release to date claimed 6.0 and none of them could show that screen on it. If
   you are on an older WordPress, staying on 1.1.1 keeps the storefront working —
   the conversion scripts have no such dependency — but the settings screen will
   not open there either.
 * Fixed: every control in the currency table was unnamed for screen readers —
    
   the enable toggle, both rate controls, both fee controls and both rounding controls.
   Each now says what it changes and which currency it belongs to.
 * Fixed: the currency picker was announced as an unlabelled button, because its
   
   visible label was never associated with the control.
 * Declared compatibility with the Cart & Checkout Blocks. The plugin already
    worked
   with them — they read their amounts from the Store API, which is converted on
   the server — but without the declaration WooCommerce warned shop owners about
   the plugin on those screens.

#### 1.1.1

 * The settings screen no longer triggers WordPress’s deprecation notice for the
   
   36px control size; its selects and text fields opt into the 40px size that becomes
   the default in WordPress 7.1.
 * Corrected the Plugin URI, which pointed at a page that does not exist.
 * Declared WooCommerce as a required plugin, and corrected the tested-against
    
   WooCommerce range to the versions the test suite actually runs (7.4 to 10.9).

#### 1.1.0

 * Cache compatibility mode, on by default. Anonymous shop, archive and product
   
   pages are rendered in your base currency so a page cache can serve the same HTML
   to everyone, and the browser converts the displayed prices afterwards. Cart, 
   checkout, order totals, order emails and the WooCommerce REST API are still converted
   on the server.
 * The currency switcher no longer reloads the page when cache compatibility is
   
   on; it sets the cookie, converts the prices in place and refreshes the mini-cart.
 * Fixed: prices were converted on admin screens and in admin AJAX, which could
   
   write a converted price into an order line item.
 * Fixed: `wc/v3` REST reads now return the base currency unless the request
    asks
   for one, so the response no longer depends on the caller’s cookies.
 * Fixed: scheduled tasks and WP-CLI no longer convert prices.
 * Fixed: cart totals are recalculated when the visitor changes currency, so the
   
   mini-cart can no longer show an amount from the previous currency.
 * Fixed: prices inside WooCommerce block themes are no longer overwritten with
   
   base amounts after the page has loaded.
 * Fixed: a percentage fee per currency was never applied, because the admin
    screen
   and the sanitiser disagreed on the stored value.
 * Fixed: a currency with a zero exchange rate no longer falls back to showing
    
   base prices as though they were converted.
 * Fixed: rounding is now applied to shipping, fees and coupon discounts as well
   
   as product prices.
 * Fixed: shipping tax is converted along with the shipping amount.
 * The plugin now warns in the admin when cache compatibility is silently not
    being
   applied, and when a mini-cart is left in the base currency because WooCommerce’s
   cart-fragment script is not loaded.
 * New public REST endpoint `POST mhmcs/v1/convert`, rate limited to 120
    requests
   a minute per address (`mhmcs_convert_rate_limit` filter).
 * See “Known limits” above for the accepted trade-offs of cache mode.

#### 1.0.0

 * First public release.
 * All features are available to everyone: unlimited currencies, automatic
    exchange
   rate updates, geolocation-based currency detection, fixed per-product prices,
   and a WooCommerce REST API currency filter.
 * Identifiers renamed to the `mhmcs` prefix. Settings from earlier
    development
   builds are not carried over.

## Meta

 *  Version **2.1.0**
 *  Last updated **3 days ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 6.6 or higher **
 *  Tested up to **7.1**
 *  PHP version ** 7.4 or higher **
 * Tags
 * [currency](https://wordpress.org/plugins/tags/currency/)[currency switcher](https://wordpress.org/plugins/tags/currency-switcher/)
   [exchange rate](https://wordpress.org/plugins/tags/exchange-rate/)[multi currency](https://wordpress.org/plugins/tags/multi-currency/)
   [woocommerce](https://wordpress.org/plugins/tags/woocommerce/)
 *  [Advanced View](https://wordpress.org/plugins/mhm-currency-switcher/advanced/)

## Ratings

No reviews have been submitted yet.

[Your review](https://wordpress.org/support/plugin/mhm-currency-switcher/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/mhm-currency-switcher/reviews/)

## Contributors

 *   [ MHM Development Team ](https://profiles.wordpress.org/maxhandmade/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/mhm-currency-switcher/)