Exchange Rates
PerfLocale can refresh your per-language currency rates on a schedule. The scheduling, the cross-calculation, the circuit breaker and the admin UI are all in the plugin — but no rate provider ships with it. Until your site wires one up, PerfLocale contacts no exchange-rate service at all, and the Auto-Sync fields say so.
Quick Start
- Register a rate source (see Supplying rates below) — this step is not optional; nothing is bundled.
- Go to PerfLocale > Settings > Addons > WooCommerce
- Enable Per-Language Currency
- Configure a currency code for each language (the code is auto-detected from the language locale —
pl_PL → PLN,en_GB → GBP, etc. — on first add) - Enable Auto-Sync Rates
- Select your registered provider and a sync interval
- Click Sync Now to verify — the per-language rate inputs update to the freshly-fetched values and persist across page reloads. WooCommerce cart / checkout / mini-cart price conversion uses the same synced rates at runtime.
Per-row manual override. Setting manual_rate => true on a specific language entry (programmatically, or via wp option patch update perflocale_settings wc_currencies <slug> manual_rate 1) pins that row to its manually-entered value and excludes it from the auto-sync feed, even when global Auto-Sync is on. Useful for hedging contracts or B2B fixed rates. If you never wire up a provider, every row is effectively a manual rate — multi-currency display still works, the numbers just don’t move on their own.
Supplying rates
PerfLocale bundles no exchange-rate service. Earlier builds shipped several; they were removed so that installing the plugin never adds an outbound dependency you didn’t ask for. Two filter seams remain, and you need exactly one of them:
perflocale/woocommerce/exchange_rate_providers— register a provider with afetch_callback. It gets the scheduling, the provider dropdown, the “Sync Now” button, the last-sync status line and the circuit breaker for free. Best when you’re calling a rate API.perflocale/woocommerce/exchange_rates_fetched— return the rate array directly. Best when the numbers come from somewhere that isn’t an HTTP call: your ERP, a treasury feed, a hard-coded contract table.
If neither is wired, a sync run logs “No exchange-rate provider configured” and stops without making a request. If you supply rates through exchange_rates_fetched alone, leave the provider dropdown empty — the sync still runs, because PerfLocale checks for that filter before bailing out.
Choosing a provider is a decision about privacy and terms as much as accuracy: whatever you register is a third-party service your store now talks to on a schedule, and its terms and privacy policy are yours to disclose, not ours.
Sync Intervals
| Interval | Requests/day | Requests/month |
|---|---|---|
| Every Hour | 24 | ~720 |
| Every 2 Hours | 12 | ~360 |
| Every 4 Hours | 6 | ~180 |
| Every 6 Hours | 4 | ~120 |
| Every 8 Hours | 3 | ~90 |
| Every 12 Hours | 2 | ~60 |
| Once Daily (default) | 1 | ~30 |
| Once Weekly | ~0.14 | ~4 |
| Once Monthly | ~0.03 | 1 |
Choose an interval that fits within your API plan limits. For most stores, daily is sufficient since exchange rates don't change drastically within a day.
API Key Management
Because no rate provider is bundled, PerfLocale defines no exchange-rate API-key constants. (The seven credentials that do have canonical env-var / constant names are all machine-translation ones — see API Keys: Environment Variables & Constants.)
A provider registered through the filter can declare needs_key and key_setting. PerfLocale then reads perflocale_settings[key_setting] before each sync and refuses to run when it’s empty (“API key missing” in the log). It does not render an input for that key — the admin key fields disappeared along with the bundled providers — so if you use key_setting, populate it yourself, e.g. wp option patch update perflocale_settings my_rates_key '…'.
Keeping the key out of the database
The simpler and safer route is to skip key_setting entirely and read your own environment variable or constant inside fetch_callback:
'needs_key' => false, // nothing read from, or stored in, settings
'fetch_callback' => function ( string $base, array $targets, string $api_key ): array {
$key = getenv( 'MY_RATES_API_KEY' ) ?: ( defined( 'MY_RATES_API_KEY' ) ? MY_RATES_API_KEY : '' );
// ...call your API with $key, return [ 'EUR' => 0.92, ... ]
},If the key does live in the database
Anything stored in perflocale_settings whose name ends in _api_key, _token, _key, _secret or _password is redacted from PerfLocale’s settings export, so a custom provider’s key is covered automatically if you name it that way. Importing a redacted export leaves the live credential in place rather than blanking it; on a fresh site you re-enter the key by hand.
Reasons to prefer your own env var / constant over a stored setting:
- Not stored in the database (not exposed in backups or SQL dumps)
- Not visible in the WordPress admin UI
- Can differ per environment (staging vs production)
- Cannot be changed by other plugins or admin users
Custom Provider via Filter
Register your exchange rate provider using the perflocale/woocommerce/exchange_rate_providers filter. This is the seam that used to hold the bundled providers, so the shape is unchanged:
add_filter( 'perflocale/woocommerce/exchange_rate_providers', function( array $providers ): array {
$providers['my_custom_api'] = [
'name' => 'My Custom API',
'needs_key' => true,
'key_setting' => 'wc_my_custom_key',
'fetch_callback' => function( string $base_currency, array $target_currencies, string $api_key ): array {
$response = wp_remote_get( 'https://my-api.example.com/rates?base=' . $base_currency . '&key=' . $api_key );
if ( is_wp_error( $response ) ) {
return [];
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
$rates = [];
foreach ( $target_currencies as $code ) {
if ( isset( $body['rates'][ $code ] ) ) {
$rates[ $code ] = (float) $body['rates'][ $code ];
}
}
return $rates;
},
];
return $providers;
} );Once registered, the provider appears in the Exchange Rate Provider dropdown on the WooCommerce settings subtab (which otherwise just explains that nothing is registered). fetch_callback receives the store’s base currency, the target codes derived from your per-language currency table, and the value of key_setting — an empty string when needs_key is false. Return [ 'EUR' => 0.92, … ]; return [] on failure and PerfLocale feeds the fx_sync circuit breaker, which skips subsequent ticks for a cooldown instead of retrying into an outage.
Hooks Reference
Filters
perflocale/woocommerce/exchange_rate_providers
Register the exchange rate providers available to the store. The default list is empty.
add_filter( 'perflocale/woocommerce/exchange_rate_providers', function( $providers ) {
// Add a provider (see example above)
$providers['my_api'] = [ ... ];
// Remove one another plugin registered
unset( $providers['someone_elses'] );
return $providers;
} );perflocale/woocommerce/exchange_rates_fetched
Filter rates after they are fetched but before they are saved.
add_filter( 'perflocale/woocommerce/exchange_rates_fetched', function( array $rates, string $base, string $provider ): array {
// Add a margin to all rates
foreach ( $rates as $code => $rate ) {
$rates[ $code ] = $rate * 1.02; // 2% markup
}
return $rates;
}, 10, 3 );Actions
perflocale/woocommerce/exchange_rates_synced
Fires after rates are successfully saved.
add_action( 'perflocale/woocommerce/exchange_rates_synced', function( array $rates, string $base, string $provider ): void {
// Log the sync
error_log( sprintf( 'Exchange rates synced from %s: %s', $provider, wp_json_encode( $rates ) ) );
// Clear page cache
if ( function_exists( 'wp_cache_flush' ) ) {
wp_cache_flush();
}
}, 10, 3 );Troubleshooting
Rates not updating
- Check that WP-Cron is running. Some hosts disable WP-Cron; you may need a server-side cron job:
*/5 * * * * curl -s https://example.com/wp-cron.php > /dev/null 2>&1 - Click Sync Now in the settings to test manually.
- Enable
WP_DEBUGandWP_DEBUG_LOGinwp-config.phpto see sync errors:
Checkdefine( 'WP_DEBUG', true ); define( 'WP_DEBUG_LOG', true );wp-content/debug.logfor lines starting withPerfLocale Exchange Rate Sync:.
"No exchange-rate provider configured"
Expected on a fresh install — nothing is bundled. Register one through perflocale/woocommerce/exchange_rate_providers and select it in the dropdown, or return rates straight from perflocale/woocommerce/exchange_rates_fetched and leave the dropdown empty.
"API key missing" error
Your provider declared needs_key with a key_setting, and that setting is empty. PerfLocale renders no input for it, so write the value yourself (wp option patch update perflocale_settings <key_setting> '…') — or drop needs_key and resolve the credential inside fetch_callback.
Rates differ from expected
- Free API tiers often return the previous day’s rates.
- Cross-calculation (when your source’s base differs from your store base) introduces minor rounding.
- Different data sources disagree slightly; two providers rarely return identical numbers.
"No rates returned" error
- Your
fetch_callback(or theexchange_rates_fetchedfilter) returned an empty array — the log line names which one. - Check that your target currency codes are valid ISO 4217 codes (e.g., EUR, GBP, JPY).
- Repeated empty results — or a response whose rates are all non-numeric or non-positive — trip the
fx_synccircuit breaker; the next few scheduled ticks are skipped until it cools down.