How to Fix Hardcoded Prices Breaking Shopify Markets Multi-Currency

A health and wellness DTC brand came to me with a simple problem: their international customers were seeing wrong prices. The store used Shopify Markets to sell across 236 regions, but prices on promotional sections, announcement bars, and comparison tables showed USD regardless of the visitor’s country.

The root cause: 38 hardcoded USD prices scattered across their theme code. Every one of them had been written as static text ("$29.99") instead of using Shopify’s Liquid money filters.

A previous developer had tried to fix this with an async JavaScript price rewriter that detected the visitor’s currency and swapped the text after page load. The script was 90 lines of JavaScript that ran on every page. It worked sometimes. But it caused a visible flash where prices loaded in USD, then jumped to the local currency 200-400ms later.

That flash registered as layout shift. CLS went from passing (0.00) to failing (0.11). Core Web Vitals broke. And international customers saw prices change in front of their eyes, which is a trust killer.

The fix was straightforward: find every hardcoded price, replace it with a Liquid money filter, and delete the JavaScript rewriter entirely.

Why Hardcoded Prices Break International Stores

Shopify’s multi-currency system works at the Liquid rendering level. When a visitor from Germany views your store, Shopify’s server converts prices before the HTML is sent to the browser. But this only applies to prices output through Liquid objects:

{{ product.price | money }}              <!-- Converts correctly -->
{{ cart.total_price | money }}            <!-- Converts correctly -->
{{ variant.compare_at_price | money }}    <!-- Converts correctly -->

Anything written as static text is invisible to the conversion system:

<p>Starting at $29.99</p>                <!-- Always shows USD -->
<span>Save $10 on your first order</span> <!-- Always shows USD -->
<td>$49.99/month</td>                    <!-- Always shows USD -->

The German visitor sees a mix of EUR prices (on product cards) and USD prices (on banners and promotional sections). This looks broken and erodes trust.

Where Hardcoded Prices Hide

After auditing dozens of Shopify themes for multi-currency issues, these are the most common locations:

Announcement Bars

<!-- BAD: Hardcoded -->
Free shipping on orders over $50

<!-- GOOD: Dynamic -->
Free shipping on orders over {{ 5000 | money }}

Note: 5000 is the amount in cents (Shopify stores all prices in the smallest currency unit). For USD, $50.00 = 5000 cents.

Section Schema Defaults

Many themes use text settings in section schemas that include prices:

{
  "type": "text",
  "id": "promo_text",
  "label": "Promotional text",
  "default": "Starting at $29.99"
}

These values get stored in settings_data.json and are output as plain text. They never pass through currency conversion.

Fix: Use a separate price type setting or a number setting that you pipe through | money in the Liquid template:

{
  "type": "text",
  "id": "promo_price_cents",
  "label": "Promotional price (in cents)",
  "default": "2999",
  "info": "Enter price in cents. 2999 = $29.99"
}
Starting at {{ section.settings.promo_price_cents | times: 1 | money }}

The | times: 1 ensures the string is treated as a number before the money filter processes it.

Comparison Tables and Pricing Grids

Subscription tiers, bundle pricing, and “What’s Included” tables frequently use hardcoded prices. Every cell needs to pull from a dynamic source or use the cents-to-money pattern above.

Metafield Values

If you store prices in metafields as text strings ("$49.99"), those will not convert either. Use Shopify’s money metafield type, or store the value in cents and pipe through | money when rendering.

Rich Text Content Blocks

Theme editor rich text blocks, blog post content, and page content with prices are all static text. If you mention a price in a blog post (“Our kits start at $399”), that will show as USD globally.

For blog and page content, there is no clean Liquid solution since the content is free-form text. The practical approach is to avoid mentioning specific prices in content, or use relative language (“starting under $400” becomes “starting at an affordable price point”).

The JavaScript Rewriter Anti-Pattern

When developers discover the hardcoded price problem, the instinct is to write JavaScript that finds price strings in the DOM and replaces them:

// DON'T DO THIS
document.querySelectorAll('[data-currency-convert]').forEach(el => {
  const usd = parseFloat(el.textContent.replace('$', ''));
  const converted = usd * exchangeRate;
  el.textContent = currencySymbol + converted.toFixed(2);
});

This approach has three problems:

1. Layout shift. The page renders with USD prices, then JavaScript rewrites them. The text changes size (EUR prices are formatted differently than USD), containers resize, and CLS spikes. On the health brand I mentioned, this caused CLS of 0.11 against a 0.10 threshold.

2. Flash of wrong prices. For 200-400ms, every visitor sees USD prices before the script runs. International visitors see the price change in front of their eyes. This looks like a pricing error and destroys trust.

3. Fragility. The script depends on finding specific DOM patterns. Theme updates, section reordering, or new content blocks can break the selector logic silently. Prices stop converting with no visible error.

The fix on the health brand: I deleted the entire 90-line JavaScript rewriter and replaced all 38 hardcoded prices with Liquid money filters. CLS dropped from 0.11 to 0.00. No more price flashing. Currency conversion works server-side before the page reaches the browser.

Step-by-Step Fix Process

Step 1: Find All Hardcoded Prices

Search your theme code for your currency symbol. For USD stores:

grep -rn '\$[0-9]' sections/ snippets/ templates/ layout/
grep -rn '\$[0-9]' config/settings_data.json

Also search for common price patterns in the theme editor: open each section in the customizer and look for text fields containing prices.

Step 2: Categorize Each Instance

For each hardcoded price, determine the source:

  • Liquid template code - Replace with {{ amount_in_cents | money }}
  • Section schema default - Change to a cents input field + money filter
  • settings_data.json stored value - Update in theme customizer after changing the schema
  • Metafield value - Change metafield type or add money filter to the template
  • Blog/page content - Remove specific prices or accept they will not convert

Step 3: Replace Using the Money Filter

The core pattern:

<!-- Static amount you control -->
{{ 5000 | money }}

<!-- Amount from a setting (stored as cents string) -->
{{ section.settings.price_cents | times: 1 | money }}

<!-- Product price (already in cents) -->
{{ product.price | money }}

<!-- Formatted with currency code -->
{{ product.price | money_with_currency }}

<!-- Without currency symbol -->
{{ product.price | money_without_currency }}

Step 4: Delete Any JavaScript Price Rewriters

Search for scripts that manipulate price text in the DOM. Common patterns:

grep -rn 'currency' assets/*.js snippets/*.liquid
grep -rn 'money\|exchange\|convert' assets/*.js

If you find an async price conversion script, delete it after confirming all prices now use Liquid money filters.

Step 5: Test Every Market

After deploying, test your store in at least 3 different currencies:

  1. Change your market preview in Shopify Admin > Settings > Markets > Preview
  2. Or use a VPN to visit from different countries
  3. Check: announcement bar, hero section, product cards, PDP, cart, and any promotional sections

Every price should display in the local currency with correct formatting. No flashing. No layout shift.

Common Money Filter Mistakes

Using | money on a string instead of a number. If a setting stores “29.99” as text, | money may produce unexpected results. Multiply by 100 first to convert to cents: {{ section.settings.price | times: 100 | money }}.

Confusing dollars and cents. Shopify stores prices in the smallest currency unit (cents for USD, pence for GBP). {{ 50 | money }} outputs $0.50, not $50.00. For $50.00, use {{ 5000 | money }}.

Hardcoding currency symbols in Liquid. Never write ${{ product.price | money_without_currency }}. The | money filter already includes the correct symbol for the visitor’s currency. Adding a dollar sign manually breaks every non-USD market.

Forgetting compare-at prices. If your product cards show both the current price and the original price, both need money filters. A common pattern is dynamic current price but hardcoded compare-at in a custom badge or savings calculation.

The Liquid | money filter is the single most important filter for international Shopify stores. Use it on every price, everywhere, without exception. For more Liquid best practices, see my Shopify Liquid Development Guide. For the complete CRO audit framework including international pricing checks, read the Shopify CRO Audit Checklist.

Frequently Asked Questions

What does hardcoded price mean in Shopify?

A hardcoded price is any price written directly in your theme code as a static number rather than pulled dynamically from Shopify's pricing system. Examples include writing $29.99 in a section's HTML, using a text block in the theme editor with a price, or storing prices in Liquid variables without running them through the money filter. These prices do not convert when Shopify Markets shows your store in a different currency.

How does Shopify Markets handle currency conversion?

Shopify Markets automatically converts prices when you use Liquid money filters (| money, | money_with_currency, | money_without_currency). When a visitor from the UK views your store, Shopify converts the product price from your base currency to GBP using the exchange rate you have configured in Markets settings. But this only works for prices output through Liquid objects like product.price, variant.price, or cart.total_price. Any price written as static text is ignored by the conversion system.

How do I find all hardcoded prices in my Shopify theme?

Search your theme code for dollar signs followed by numbers: grep for patterns like $X, $XX.XX, or the currency symbol for your base currency. Also search for price strings in settings_data.json, section schema defaults, and metafield values. Check announcement bars, promotional banners, comparison tables, and FAQ content, which are the most common locations for hardcoded prices. The Liquid money filter (| money) is your indicator of a correctly dynamic price.

What is FOUC and how do hardcoded prices cause it?

FOUC stands for Flash of Unstyled Content. In the context of multi-currency, it happens when the page first loads showing prices in your base currency (USD), then JavaScript runs to rewrite those prices to the visitor's local currency. The visitor sees prices flash and change, which causes layout shift (CLS) and looks broken. The fix is to use server-side Liquid money filters instead of client-side JavaScript price rewriting.

Does the Shopify Geolocation app fix hardcoded prices?

No. The Geolocation app handles country/currency selector UI and redirects, but it cannot convert prices that are hardcoded as static text in your theme. Only prices output through Liquid money filters are eligible for automatic currency conversion. The Geolocation app assumes your theme already uses proper Liquid pricing throughout.