--- title: Create a country selector description: Learn how to setup a country selector to allow users to choose their own country. source_url: html: https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector md: https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector.md --- ExpandOn this page * [Requirements](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector#requirements) * [Step 1: Provide a list of available countries](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector#step-1-provide-a-list-of-available-countries) * [Step 2: Create get​Locale​From​Request utility](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector#step-2-create-getlocalefromrequest-utility) * [Step 3: Add the selected locale in the root loader function](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector#step-3-add-the-selected-locale-in-the-root-loader-function) * [Step 4: Create a resource route for the available countries](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector#step-4-create-a-resource-route-for-the-available-countries) * [Step 5: Render the available countries as a form](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector#step-5-render-the-available-countries-as-a-form) * [Step 6: Handle form submit](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector#step-6-handle-form-submit) * [Step 7: Make sure re-rendering happens at the root HTML](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector#step-7-make-sure-re-rendering-happens-at-the-root-html) # Create a country selector Note This guide might not be compatible with features introduced in Hydrogen version 2025-05 and above. Check the latest [documentation](https://shopify.dev/docs/api/hydrogen) if you encounter any issues. In this guide you will learn how to create a country selector so that buyers can change the store language and currency. *** ## Requirements * You've completed the [Hydrogen getting started](https://shopify.dev/docs/storefronts/headless/hydrogen/getting-started) with a `Hello World` example project. * You've setup the regions and languages for your store with [Shopify Markets](https://help.shopify.com/en/manual/markets). * You've completed the [setup multi-region and multilingual storefront with URL paths](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/multiple-languages-url-paths) or [setup multi-region and multilingual storefront with domains and subdomains](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/multiple-languages-domains) tutorial. * You're familiar with [using the Storefront API with Shopify Markets](https://shopify.dev/docs/storefronts/headless/building-with-the-storefront-api/markets). *** ## Step 1: Provide a list of available countries Create a JSON file with a list of available countries that will be rendered at every page. You can use the `/app/data/countries.js` file in the Hydrogen demo store as a point of reference. For performance and SEO reasons, Shopify recommends using a static JSON variable for the countries. Optionally, you can create a build script that generates this file on build. The following is an example of the countries data structure: ## /app/data/countries ## /app/data/countries.js ```js export const countries = { default: { language: 'EN', country: 'US', label: 'United States (USD $)', // Labels to be shown in the country selector host: 'hydrogen.shop', // The host and pathPrefix are used for linking }, 'en-ca': { language: 'EN', country: 'CA', label: 'Canada (CAD $)', host: 'ca.hydrogen.shop', }, 'fr-ca': { language: 'EN', country: 'CA', label: 'Canada (Français) (CAD $)', host: 'ca.hydrogen.shop', pathPrefix: '/fr', }, 'en-au': { language: 'EN', country: 'AU', label: 'Australia (AUD $)', host: 'hydrogen.au', }, }; ``` ```ts import type { CountryCode, LanguageCode, } from '@shopify/hydrogen/storefront-api-types'; export type Locale = { language: LanguageCode; country: CountryCode; label: string; host: string; pathPrefix?: string }; export const countries: Record = { default: { language: 'EN', country: 'US', label: 'United States (USD $)', // Labels to be shown in the country selector host: 'hydrogen.shop', // The host and pathPrefix are used for linking }, 'en-ca': { language: 'EN', country: 'CA', label: 'Canada (CAD $)', host: 'ca.hydrogen.shop', }, 'fr-ca': { language: 'EN', country: 'CA', label: 'Canada (Français) (CAD $)', host: 'ca.hydrogen.shop', pathPrefix: '/fr', }, 'en-au': { language: 'EN', country: 'AU', label: 'Australia (AUD $)', host: 'hydrogen.au', }, }; ``` *** ## Step 2: Create get​Locale​From​Request utility Create the `getLocaleFromRequest` utility function. This function will read the request and determine the locale to be used throughout the app. You can use the `/app/lib/utils.js` file in the demo Hydrogen demo store as an example. ## /app/lib/utils ## /app/lib/utils.js ```js import {countries} from '~/data/countries'; export function getLocaleFromRequest(request) { const url = new URL(request.url); switch (url.host) { case 'ca.hydrogen.shop': if (/^\/fr($|\/)/.test(url.pathname)) { return countries['fr-ca']; } else { return countries['en-ca']; } break; case 'hydrogen.au': return countries['en-au']; break; default: return countries['default']; } } ``` ```ts import {type Locale, countries} from '~/data/countries'; export function getLocaleFromRequest(request: Request): Locale { const url = new URL(request.url); switch (url.host) { case 'ca.hydrogen.shop': if (/^\/fr($|\/)/.test(url.pathname)) { return countries['fr-ca']; } else { return countries['en-ca']; } break; case 'hydrogen.au': return countries['en-au']; break; default: return countries['default']; } } ``` *** ## Step 3: Add the selected locale in the `root` loader function This step gets the user's request and finds the associated locale. You should make the selected locale available throughout the app with the `loader`. ## /app/root ## /app/root.jsx ```jsx export async function loader({context, request}) { ... return defer({ ..., selectedLocale: await getLocaleFromRequest(request), }); } ``` ```tsx export async function loader({context, request}: LoaderArgs) { ... return defer({ ..., selectedLocale: await getLocaleFromRequest(request), }); } ``` *** ## Step 4: Create a resource route for the available countries A [Remix resource route](https://remix.run/docs/en/main/guides/resource-routes) is useful when the UI fetches the available countries to display. You can use the `/routes/($locale).api.countries.js` in the The Hydrogen demo store as an example. ## /routes/($locale).api.countries.js ## /routes/($locale).api.countries.js ```jsx import {json} from '@remix-run/server-runtime'; import {CacheLong, generateCacheControlHeader} from '@shopify/hydrogen'; import {countries} from '~/data/countries'; export async function loader() { return json( {...countries}, {headers: {'cache-control': generateCacheControlHeader(CacheLong())}}, ); } // no-op export default function CountriesResourceRoute() { return null; } ``` ```tsx import {json} from '@remix-run/server-runtime'; import {CacheLong, generateCacheControlHeader} from '@shopify/hydrogen'; import {countries} from '~/data/countries'; export async function loader() { return json( {...countries}, {headers: {'cache-control': generateCacheControlHeader(CacheLong())}}, ); } // no-op export default function CountriesResourceRoute() { return null; } ``` *** ## Step 5: Render the available countries as a form Create a `CountrySelector` component using [Remix Forms](https://remix.run/docs/en/v1/components/form): You can use the `app/components/CountrySelector.js` file in the the Hydrogen demo store as an example. ## /app/components/CountrySelector ## /app/components/CountrySelector.jsx ```jsx import {Form, useMatches, useLocation, useFetcher} from '@remix-run/react'; import {useEffect, useState} from 'react'; export function CountrySelector() { const [root] = useMatches(); const selectedLocale = root.data.selectedLocale; const {pathname, search} = useLocation(); const [countries, setCountries] = useState({}); // Get available countries list const fetcher = useFetcher(); useEffect(() => { if (!fetcher.data) { fetcher.load('/api/countries'); return; } setCountries(fetcher.data); }, [countries, fetcher.data]); const strippedPathname = pathname.replace(selectedLocale.pathPrefix, ''); return (
{selectedLocale.label}
{countries && Object.keys(countries).map((countryKey) => { const locale = countries[countryKey]; const hreflang = `${locale.language}-${locale.country}`; return (
); })}
); } ``` ```tsx import {Form, useMatches, useLocation, useFetcher} from '@remix-run/react'; import {useEffect, useState} from 'react'; import type {Locale} from '~/data/countries'; export function CountrySelector() { const [root] = useMatches(); const selectedLocale = root.data.selectedLocale; const {pathname, search} = useLocation(); const [countries, setCountries] = useState>({}); // Get available countries list const fetcher = useFetcher(); useEffect(() => { if (!fetcher.data) { fetcher.load('/api/countries'); return; } setCountries(fetcher.data); }, [countries, fetcher.data]); const strippedPathname = pathname.replace(selectedLocale.pathPrefix, ''); return (
{selectedLocale.label}
{countries && Object.keys(countries).map((countryKey) => { const locale = countries[countryKey]; const hreflang = `${locale.language}-${locale.country}`; return (
); })}
); } ``` *** ## Step 6: Handle form submit Create the `/app/routes/($locale).jsx` route that will handle the form submit action ## /app/routes/($locale) ## /app/routes/($locale).jsx ```jsx import {redirect} from '@shopify/remix-oxygen'; import invariant from 'tiny-invariant'; import {countries} from '~/data/countries'; export const action = async ({request, context}) => { const {session} = context; const formData = await request.formData(); // Make sure the form request is valid const languageCode = formData.get('language'); invariant(languageCode, 'Missing language'); const countryCode = formData.get('country'); invariant(countryCode, 'Missing country'); // determine where to redirect to relative to where user navigated from // ie. hydrogen.shop/collections -> ca.hydrogen.shop/collections const path = formData.get('path'); const toLocale = countries[`${languageCode}-${countryCode}`.toLowerCase()]; const cartId = await session.get('cartId'); // Update cart buyer's country code if there is a cart id if (cartId) { await updateCartBuyerIdentity(context, { cartId, buyerIdentity: { countryCode, }, }); } const redirectUrl = new URL( `${toLocale.pathPrefix || ''}${path}`, `https://${toLocale.host}` ); return redirect(redirectUrl, 302); }; async function updateCartBuyerIdentity({storefront}, {cartId, buyerIdentity}) { const data = await storefront.mutate<{ cartBuyerIdentityUpdate: {cart}; }>(UPDATE_CART_BUYER_COUNTRY, { variables: { cartId, buyerIdentity, }, }); const UPDATE_CART_BUYER_COUNTRY = `#graphql mutation CartBuyerIdentityUpdate( $cartId: ID! $buyerIdentity: CartBuyerIdentityInput! ) { cartBuyerIdentityUpdate(cartId: $cartId, buyerIdentity: $buyerIdentity) { cart { id } } } `; ``` ```tsx import type { CountryCode, LanguageCode, CartBuyerIdentityInput, Cart, } from '@shopify/hydrogen/storefront-api-types'; import {redirect, type AppLoadContext, type ActionFunction} from '@shopify/remix-oxygen'; import invariant from 'tiny-invariant'; import {countries} from '~/data/countries'; export const action: ActionFunction = async ({request, context}) => { const {session} = context; const formData = await request.formData(); // Make sure the form request is valid const languageCode = formData.get('language') as LanguageCode; invariant(languageCode, 'Missing language'); const countryCode = formData.get('country') as CountryCode; invariant(countryCode, 'Missing country'); // Determine where to redirect to relative to where user navigated from // ie. hydrogen.shop/collections -> ca.hydrogen.shop/collections const path = formData.get('path'); const toLocale = countries[`${languageCode}-${countryCode}`.toLowerCase()]; const cartId = await session.get('cartId'); // Update cart buyer's country code if there is a cart id if (cartId) { await updateCartBuyerIdentity(context, { cartId, buyerIdentity: { countryCode, }, }); } const redirectUrl = new URL( `${toLocale.pathPrefix || ''}${path}`, `https://${toLocale.host}`, ).toString(); return redirect(redirectUrl, 302); }; async function updateCartBuyerIdentity( {storefront}: AppLoadContext, { cartId, buyerIdentity, }: { cartId: string; buyerIdentity: CartBuyerIdentityInput; }, ) { const data = await storefront.mutate<{ cartBuyerIdentityUpdate: {cart: Cart}; }>(UPDATE_CART_BUYER_COUNTRY, { variables: { cartId, buyerIdentity, }, }); invariant(data, 'No data returned from Shopify API'); return data.cartBuyerIdentityUpdate.cart; } const UPDATE_CART_BUYER_COUNTRY = `#graphql mutation CartBuyerIdentityUpdate( $cartId: ID! $buyerIdentity: CartBuyerIdentityInput! ) { cartBuyerIdentityUpdate(cartId: $cartId, buyerIdentity: $buyerIdentity) { cart { id } } } `; ``` *** ## Step 7: Make sure re-rendering happens at the root HTML Make sure to provide a `key` to the components that will change due to localization. Specially for URL path localization schemes. Sometimes, React won't know when to re-render a component. To avoid this problem, add localization as key in the `App`. ## /app/root ## /app/root.jsx ```jsx export default function App() { const data = useLoaderData(); const locale = data.selectedLocale; return ( ); } ``` ```tsx export default function App() { const data = useLoaderData(); const locale = data.selectedLocale; return ( ); } ``` *** * [Requirements](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector#requirements) * [Step 1: Provide a list of available countries](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector#step-1-provide-a-list-of-available-countries) * [Step 2: Create get​Locale​From​Request utility](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector#step-2-create-getlocalefromrequest-utility) * [Step 3: Add the selected locale in the root loader function](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector#step-3-add-the-selected-locale-in-the-root-loader-function) * [Step 4: Create a resource route for the available countries](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector#step-4-create-a-resource-route-for-the-available-countries) * [Step 5: Render the available countries as a form](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector#step-5-render-the-available-countries-as-a-form) * [Step 6: Handle form submit](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector#step-6-handle-form-submit) * [Step 7: Make sure re-rendering happens at the root HTML](https://shopify.dev/docs/storefronts/headless/hydrogen/markets/country-selector#step-7-make-sure-re-rendering-happens-at-the-root-html)