Architecture

localization.country is not geolocation (and most Shopify country-blocking snippets are broken)

Nearly every Shopify geo-blocking tutorial checks localization.country. That property is a shopper-controlled dropdown, not the visitor's location. Here's what it really means and how to detect country correctly.

Bas Lefeber

Founder, learnshopify.dev · August 3, 2026 · 7 min read

Ready to learn Shopify development?Short, interactive lessons where you write real Liquid against a live storefront and watch it change as you type. Free, and genuinely fun.

Search for how to block a country on Shopify and you will find the same snippet reproduced across a dozen blogs, each one confidently telling you to paste it into theme.liquid. It looks like this, give or take a variable name:

the snippet everyone copies
{% assign blocked = "CN,RU,VN" | split: "," %} {% if blocked contains localization.country.iso_code %}  <script>window.location.href = "/pages/unavailable";</script>{% endif %}

It is syntactically valid Liquid. It will render without errors. And it does not do what the article says it does, because localization.country is not the visitor's location. It is a value the visitor chooses.

Straight from the docs

Shopify's own reference for the localization object defines the property as: "country: The currently selected country on the storefront." Selected, not detected. It is the state of the country picker in your header, and it is a shopper-facing control.

What localization.country actually is

The localization object exists to power the market and currency switcher in your theme. Its four properties are all about what the shopper has chosen to browse as:

PropertyMeaning
localization.countryThe currently selected country on the storefront
localization.languageThe currently selected language
localization.marketThe currently selected market
localization.available_countriesEvery country the store is configured to sell to
The localization object. None of these is a geo-IP lookup.

Shopify does seed the initial selection sensibly, which is exactly why the bug survives review. A first-time visitor from Berlin often does land on the German market, so the developer tests the snippet, sees the right country, and ships it. The property was never a location signal, though. It was a preference that happened to start with a reasonable default.

Two different concepts that read the same in a template. Only one of them is available in Liquid, and it is the one a visitor controls.

Three ways the snippet fails

The gap between "selected" and "detected" is not academic. It shows up as three distinct failures, in increasing order of how much they will cost you.

1. Anyone can switch it, in one click

The country picker is a control you put in your own header. A visitor in a blocked country opens it, picks a different country, and the gate opens. No VPN, no dev tools, no technical knowledge. The person you were most trying to stop is the person most motivated to click the dropdown.

2. The redirect runs after the page is delivered

Look again at what the snippet emits: a <script> tag that sets location.href. By the time that executes, the full page is already in the browser. Anyone with JavaScript disabled sees everything. Anyone running curl sees everything. Every scraper on earth sees everything, since scrapers are precisely the clients that do not execute your JavaScript. As access control it is a redirect for people who were not trying.

3. It poisons the CDN cache

This is the one that actually breaks stores, and almost nobody mentions it. Shopify renders Liquid once and caches the HTML at the edge for many visitors. If your template emits different markup depending on a per-visitor value, you have made the cached copy visitor-specific. A shopper in an allowed country can be served the cached page that contains the block redirect, and a blocked visitor can be served the cached page that does not.

Why this is the deepest problem

The first two failures make the gate weak. The third makes it non-deterministic, which is worse, because it fails intermittently in a way that looks like a ghost. Support tickets that read "a customer in France got redirected to the unavailable page and I can't reproduce it" are usually this. Any Liquid branching on per-visitor state has this problem, not only geo-blocking.

What localization.country is genuinely good for

Do not conclude the property is useless. It is the correct tool for everything downstream of the shopper's own choice, which is a large and useful category:

  • Rendering the country picker itself. Loop localization.available_countries and mark the current one. That is the property's whole purpose.
  • Market-specific content. Different size guides, shipping copy, or compliance text per market, once the shopper is in that market.
  • Flags. Passing a country object to image_url returns a CDN URL for that country's flag, normalised to 4:3.
a correct use of the object
{{ localization.country | image_url: width: 32 | image_tag }} {% for country in localization.available_countries %}  {{ country.name }} ({{ country.iso_code }}) {{ country.currency.iso_code }}{% endfor %}

The distinction to hold onto: localization tells you which storefront experience the shopper is in. It never tells you where their connection came from.

Learn this properly · free lesson

Themes that survive the merchant: defensive Liquid

Trusting a value that a visitor controls is one of the classic Shopify theme bugs. Practise writing Liquid that holds up when the data isn't what you assumed. Free lesson, no signup.

Try this lesson — free

Detecting country correctly, by requirement

There is no single replacement, because "block a country" means four different things depending on who is asking. Pick the row that matches yours.

RequirementCorrect mechanismNative?
Don't ship thereShipping zones. Leave the country out of every zone.Yes
Don't sell thereShopify Markets. Deactivate the market so checkout is unavailable.Yes
Suggest the right marketClient-side geo hint plus the country picker. Cosmetic by design.Yes
Deny access to the storefrontA layer in front of the page. Not Liquid, not client-side JS.No
What you actually want, and where it belongs.

The first two rows cover most real merchant intent and cost nothing. If someone says "block Russia," they usually mean "stop accepting Russian orders," and Markets does that natively, at checkout, deterministically, with no code and no cache implications. Reach for it before anything else. Our step-by-step guide to blocking a country on Shopify walks through both native routes with the exact admin paths.

The fourth row is the genuinely hard one, and it is hard for the reason the whole post has been circling: on a Shopify-hosted storefront you cannot sit in front of the request. Cloudflare's proxy is explicitly unsupported on Shopify domains, Liquid renders too late and too cached, and client-side JS runs after delivery. What is left is a fast decision service reached from an app embed, which is what every blocking app on the App Store is underneath.

Country blocking that uses real geolocation

Cordon is built on that architecture, and it is a useful reference point because it does the thing the broken snippet only pretends to: the country comes from a geo-IP lookup on the actual connection, not from a dropdown the visitor controls. That single difference resolves all three failures above at once. There is nothing to switch, the decision happens before the page is usable rather than after, and it never branches the cached Liquid render.

The country rule states the policy. The traffic-type rules are what make it enforceable.

What it gives you beyond the country list itself:

  • 200+ countries, one click each, with a flag picker and presets for common cases like blocking China or turning on anti-scraper mode.
  • The bypass path closed. Blocking a country is close to meaningless if a visitor can install a VPN and walk around it, so live VPN, residential-proxy and Tor-exit detection is the half that actually makes a country rule hold.
  • Finer targeting than a country. Individual IPs, CIDR ranges, whole ASNs, and datacenter networks, for when the real pattern is one network rather than one nation.
  • A customisable block screen. A branded explanation beats a broken page, and a redirect target is available if you would rather send blocked visitors somewhere specific.
  • A visitor log that shows the reasoning. Country, network, and the exact rule that fired, which is what you need when a merchant asks why a particular customer was stopped. IPs are SHA-256 hashed with a daily-rotating salt, so the log is useful without becoming a GDPR liability.

The two things to check on any tool here

Does it fail open? This code sits in the critical path of every page load. Cordon allows every visitor through if its detection service is unreachable, which is the only safe default. And does it protect your crawlers? Verified search engines are exempted by reverse DNS rather than by a spoofable user-agent, and iCloud Private Relay is allowlisted by default so Apple users are not caught by proxy rules. Get either of those wrong and you will deindex the store or take it offline.

Country blocking and IP rules are on the free plan, so you can verify the behaviour on a real store before paying anything; live VPN and proxy detection starts at Growth. For the merchant-side click-through there is a step-by-step guide to blocking a country on Shopify, and a guide to geo-restricting a store to specific countries if your requirement is an allowlist rather than a blocklist.

Whatever you choose, the thing worth carrying to the next problem is the habit: before you branch on a value, check the docs for who controls it. localization.country reads like a fact about the visitor and is actually a preference they set. Shopify's object model is full of properties that are one word away from meaning something entirely different, and the docs are always faster than the debugging.

Frequently asked questions

What does localization.country return in Shopify Liquid?

The currently selected country on the storefront, per Shopify's own Liquid reference. It reflects the shopper's choice in the country or market picker, not a geo-IP lookup of their connection. Shopify seeds an initial selection sensibly, which is why it often looks like geolocation in testing, but the visitor can change it at any time.

Can I detect a visitor's country in Shopify Liquid?

No. Liquid has no geolocation data. The request object exposes only design_mode, host, locale, origin, and page_type, and localization.country is the shopper-selected country rather than a detected one. Country detection has to happen either in the browser after the page is delivered, or at a layer in front of the storefront such as an app's decision service.

Why doesn't the Liquid country-blocking snippet work?

Three reasons. It reads localization.country, which the visitor can change in one click from the country picker. It typically emits a JavaScript redirect that runs only after the full page has already been delivered, so curl and scrapers see everything. And branching Liquid on a per-visitor value poisons Shopify's CDN cache, so the block fires intermittently for the wrong people.

How do I stop selling to a specific country on Shopify?

Use Shopify Markets and deactivate the market, which makes checkout unavailable for that country, or leave the country out of every shipping zone so no shipping rate is available. Both are native, free, deterministic, and enforced at checkout rather than in the theme. This covers most merchant intent behind "block a country".

How do I actually block a country on Shopify?

If you mean stop selling or shipping there, use Shopify Markets or shipping zones, which are native, free, and enforced at checkout. If you mean deny access to the storefront, you need a layer with real geolocation, which means an app. Cordon blocks 200+ countries by geo-IP on the actual connection rather than the shopper-selected country, and pairs it with VPN, proxy and Tor detection so the rule cannot be walked around. Country blocking and IP rules are on its free plan.

Can visitors bypass Shopify country blocking with a VPN?

Yes, if the tool only checks country. Any country rule based purely on geo-IP is defeated by a VPN or proxy that presents an IP somewhere else, which is why VPN, residential-proxy and Tor-exit detection is the half that makes a country rule hold. Cordon pairs the two, with live detection rather than a static IP list, since the point of a rotating proxy is that yesterday's list is already wrong.

What is localization.country actually useful for?

Anything that follows from the shopper's own choice: rendering the country and market picker via localization.available_countries, showing market-specific content such as shipping copy or size guides, and rendering country flags by passing a country object to the image_url filter. It tells you which storefront experience the shopper is in, never where their connection came from.

Start free

Ready to become a Shopify developer?

You just read how it works. Now write it yourself: real tickets from a live store, in an editor where the storefront updates as you type. Module 1 is free, no card.

Start your first lesson

Free · No credit card · Your first win in minutes

LiquidShopify object modeltheme-architecturesecurity

About the author

Bas Lefeber, Founder, learnshopify.dev

Bas builds learnshopify.dev, where developers learn production-grade Shopify theme development against a live storefront. He writes about Liquid, theme architecture, and the parts of the job that still matter now that AI writes the code.

Keep going in the curriculum