E-commerce

Building a Store Finder for Your Shopify Store

Building a Store Finder for Your Shopify Store

Building a Store Finder for Your Shopify Store

December 23, 2025

December 23, 2025

Many Shopify merchants operate both online and brick-and-mortar locations. However, customers often struggle to find the nearest physical store and accurate directions. This comprehensive tutorial addresses this issue by demonstrating how developers can surface real-time location data and build an intuitive store-finder interface directly within a Shopify storefront. We’ll apply customizations to the Dawn theme to enable this functionality.

Table of Contents

  1. Getting Started

  2. Create a Store Finder Template

  3. Set Up the Storefront API Connection

  4. Render Store Locations

  5. Get the User’s Geolocation

  6. Allow Search by Location

  7. Limitations

  8. Summary

Getting Started

For this solution, we’ll be using the Storefront API to pull locations close to the user. For a low-code approach, consider checking the Shopify App Store.

Pre-requisites:

  • Knowledge of theme development, GraphQL, and the Storefront API.

  • Ensure your store has multiple locations.

  • Generate a Storefront API key.

  • Generate a Google Maps geocoding API key.

1. Create a Store Finder Template

Our first goal is to create a /pages/store-finder route that the customer can access. In the theme code, create a store finder section sections/store-finder.liquid:

{% schema %} { "name": "Store Finder", "settings": [] } {% endschema %}

Next, create a store finder template templates/page.store-finder.json:

{ "sections": { "store-finder": { "type": "store-finder" } }, "order": [ "store-finder" ] }

Now, assign the template to a page:

  1. From the Shopify admin, create a new Page (Online Store > Pages).

  2. Set the title to “Store Finder”, leave the content blank, and select ‘store-finder’ as the template.

You should now be able to access /pages/store-finder.

2. Set Up the Storefront API Connection

You’ll need a Storefront API key for this step! Liquid does not offer the ability to iterate through all locations. To render the initial view of store locations, we’ll make a client-side fetch to Shopify’s Storefront API, which is a public GraphQL-based interface.

Add a Storefront API access token field to your sections/store-finder.liquid section schema:

{% schema %} { "name": "Store Finder", "settings": [ { "type": "text", "id": "storefront_access_token", "label": "Storefront API access token" } ] } {% endschema %}

Integrate the Storefront API token and domain as data attributes:

data-storefront-api-key="{{ section.settings.storefront_access_token | escape }}" data-domain="{{ shop.permanent_domain }}"

3. Render Store Locations

Next, you’ll make a request to the Storefront API using the data attributes you set up in the previous step. Add the following JavaScript to sections/store-finder.liquid:

(() => { const ROOT = document.getElementById('store-finder-root'); const DOMAIN = ROOT.dataset.domain; const TOKEN = ROOT.dataset.storefrontApiKey; const query = `#graphql query ($coord: GeoCoordinateInput, $sortKey: LocationSortKeys) { locations(first: 100, near: $coord, sortKey: $sortKey) { nodes { id name address { address1 address2 city provinceCode country zip phone latitude longitude } } } }`; const formatAddress = (a) => [ a.address1, a.address2, `${a.city}${a.provinceCode ? ', ' + a.provinceCode : ''} ${a.zip}`, a.country, ] .filter(Boolean) .join(' '); async function fetchLocations(coords) { const variables = { coord: coords || null, sortKey: coords ? 'DISTANCE' : null }; const resp = await fetch(`https://${DOMAIN}/api/2025-04/graphql.json`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Shopify-Storefront-Access-Token': TOKEN, }, body: JSON.stringify({ query, variables }), }); const { data, errors } = await resp.json(); if (errors) { console.error(errors); ROOT.innerHTML = ' Sorry—store locations could not be loaded. '; return []; } return data.locations.nodes; } function renderLocations(list = []) { if (!list.length) { ROOT.innerHTML = ' No locations found. '; return; } const html = list .map( (loc) => ` ${loc.name} ${formatAddress(loc.address)} Get directions ` ) .join(''); ROOT.innerHTML = ` ${html} `; } fetchLocations().then(renderLocations); })();

You should now see a list of all stores.

4. Get the User’s Geolocation

Browsers expose a built-in Geolocation API that can return the visitor’s latitude and longitude. Once we have coordinates, we’ll pass them to fetchLocations() to sort results by distance.

Place the following snippet above the form in your section:

.store-finder__form { display: flex; gap: .5rem; margin-bottom: 1.5rem; } .store-finder__form input { flex: 1 1 auto; padding: .6rem .8rem; border: 1px solid #d1d5db; border-radius: 4px; } .store-finder__form button { border: none; background: #1a73e8; color: #fff; padding: .6rem .8rem; border-radius: 4px; cursor: pointer; } .store-finder__form button[disabled] { opacity: .6; cursor: not-allowed; } .visually-hidden { position: absolute !important; clip: rect(1px,1px,1px,1px); }

Finally, include the additional JavaScript to return locations sorted by distance from the user:

const btn = document.getElementById("sf-use-my-location"); const input = document.getElementById("sf-address"); input.removeAttribute("disabled"); btn.addEventListener("click", () => { if (!navigator.geolocation) { alert("Geolocation is not supported by your browser."); return; } btn.disabled = true; btn.innerHTML = "…"; navigator.geolocation.getCurrentPosition( ({ coords }) => { const { latitude, longitude } = coords; fetchLocations({ latitude, longitude }).then(renderLocations); btn.innerHTML = "✓"; }, (err) => { console.warn(err); alert( "Sorry, we couldn’t get your location. You can enter an address instead." ); btn.innerHTML = "⚠"; }, { enableHighAccuracy: true, timeout: 10000, maximumAge: 600000 } ); });

5. Allow Search by Location

Some customers may block geolocation or want to search another city. With this step, you’ll let them type a city, postal code, or full address, then geocode it with the Google Maps Geocoding API.

Update your sections/store-finder.liquid section schema to include a Google Maps API Key field:

{% schema %} { "name": "Store Finder", "settings": [ { "type": "text", "id": "storefront_access_token", "label": "Storefront API access token" }, { "type": "text", "id": "google_maps_api_key", "label": "Google Maps API key" } ] } {% endschema %}

Handle form submission by adding an event listener:

async function geocodeAddress(address) { if (!GOOGLE_KEY) throw new Error('Missing Google Maps API key'); const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent( address )}&key=${GOOGLE_KEY}`; const res = await fetch(url); const data = await res.json(); if (data.status !== 'OK' || !data.results.length) { throw new Error('No geocoding results'); } const loc = data.results[0].geometry.location; return { latitude: loc.lat, longitude: loc.lng }; } form.addEventListener('submit', async (e) => { e.preventDefault(); const addr = input.value.trim(); if (!addr) return; try { form.classList.add('is-loading'); const { latitude, longitude } = await geocodeAddress(addr); fetchLocations({ latitude, longitude }).then(renderLocations); } catch (err) { console.warn(err); alert('Sorry, we couldn’t locate that address.'); } finally { form.classList.remove('is-loading'); } });

Limitations

While this expands Shopify’s functionality, it does come with constraints:

  • This approach displays stores set up as ‘locations’ and that offer pickup. There may be scenarios where storing data in metaobjects would be preferable.

  • Our initial load and search functionality rely on JavaScript. If a user has JavaScript disabled, the store finder won’t load.

Summary

Combining Shopify’s Online Store with the Storefront API can enable a ton of flexibility. Be sure to test these solutions thoroughly before deploying and consider edge cases. Each theme will have different features that might need adjusting for a smooth user experience.

To take this to the next step, consider adding store locations as markers on a Google Map using the Google API.

Go further

Many Shopify merchants operate both online and brick-and-mortar locations. However, customers often struggle to find the nearest physical store and accurate directions. This comprehensive tutorial addresses this issue by demonstrating how developers can surface real-time location data and build an intuitive store-finder interface directly within a Shopify storefront. We’ll apply customizations to the Dawn theme to enable this functionality.

Table of Contents

  1. Getting Started

  2. Create a Store Finder Template

  3. Set Up the Storefront API Connection

  4. Render Store Locations

  5. Get the User’s Geolocation

  6. Allow Search by Location

  7. Limitations

  8. Summary

Getting Started

For this solution, we’ll be using the Storefront API to pull locations close to the user. For a low-code approach, consider checking the Shopify App Store.

Pre-requisites:

  • Knowledge of theme development, GraphQL, and the Storefront API.

  • Ensure your store has multiple locations.

  • Generate a Storefront API key.

  • Generate a Google Maps geocoding API key.

1. Create a Store Finder Template

Our first goal is to create a /pages/store-finder route that the customer can access. In the theme code, create a store finder section sections/store-finder.liquid:

{% schema %} { "name": "Store Finder", "settings": [] } {% endschema %}

Next, create a store finder template templates/page.store-finder.json:

{ "sections": { "store-finder": { "type": "store-finder" } }, "order": [ "store-finder" ] }

Now, assign the template to a page:

  1. From the Shopify admin, create a new Page (Online Store > Pages).

  2. Set the title to “Store Finder”, leave the content blank, and select ‘store-finder’ as the template.

You should now be able to access /pages/store-finder.

2. Set Up the Storefront API Connection

You’ll need a Storefront API key for this step! Liquid does not offer the ability to iterate through all locations. To render the initial view of store locations, we’ll make a client-side fetch to Shopify’s Storefront API, which is a public GraphQL-based interface.

Add a Storefront API access token field to your sections/store-finder.liquid section schema:

{% schema %} { "name": "Store Finder", "settings": [ { "type": "text", "id": "storefront_access_token", "label": "Storefront API access token" } ] } {% endschema %}

Integrate the Storefront API token and domain as data attributes:

data-storefront-api-key="{{ section.settings.storefront_access_token | escape }}" data-domain="{{ shop.permanent_domain }}"

3. Render Store Locations

Next, you’ll make a request to the Storefront API using the data attributes you set up in the previous step. Add the following JavaScript to sections/store-finder.liquid:

(() => { const ROOT = document.getElementById('store-finder-root'); const DOMAIN = ROOT.dataset.domain; const TOKEN = ROOT.dataset.storefrontApiKey; const query = `#graphql query ($coord: GeoCoordinateInput, $sortKey: LocationSortKeys) { locations(first: 100, near: $coord, sortKey: $sortKey) { nodes { id name address { address1 address2 city provinceCode country zip phone latitude longitude } } } }`; const formatAddress = (a) => [ a.address1, a.address2, `${a.city}${a.provinceCode ? ', ' + a.provinceCode : ''} ${a.zip}`, a.country, ] .filter(Boolean) .join(' '); async function fetchLocations(coords) { const variables = { coord: coords || null, sortKey: coords ? 'DISTANCE' : null }; const resp = await fetch(`https://${DOMAIN}/api/2025-04/graphql.json`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Shopify-Storefront-Access-Token': TOKEN, }, body: JSON.stringify({ query, variables }), }); const { data, errors } = await resp.json(); if (errors) { console.error(errors); ROOT.innerHTML = ' Sorry—store locations could not be loaded. '; return []; } return data.locations.nodes; } function renderLocations(list = []) { if (!list.length) { ROOT.innerHTML = ' No locations found. '; return; } const html = list .map( (loc) => ` ${loc.name} ${formatAddress(loc.address)} Get directions ` ) .join(''); ROOT.innerHTML = ` ${html} `; } fetchLocations().then(renderLocations); })();

You should now see a list of all stores.

4. Get the User’s Geolocation

Browsers expose a built-in Geolocation API that can return the visitor’s latitude and longitude. Once we have coordinates, we’ll pass them to fetchLocations() to sort results by distance.

Place the following snippet above the form in your section:

.store-finder__form { display: flex; gap: .5rem; margin-bottom: 1.5rem; } .store-finder__form input { flex: 1 1 auto; padding: .6rem .8rem; border: 1px solid #d1d5db; border-radius: 4px; } .store-finder__form button { border: none; background: #1a73e8; color: #fff; padding: .6rem .8rem; border-radius: 4px; cursor: pointer; } .store-finder__form button[disabled] { opacity: .6; cursor: not-allowed; } .visually-hidden { position: absolute !important; clip: rect(1px,1px,1px,1px); }

Finally, include the additional JavaScript to return locations sorted by distance from the user:

const btn = document.getElementById("sf-use-my-location"); const input = document.getElementById("sf-address"); input.removeAttribute("disabled"); btn.addEventListener("click", () => { if (!navigator.geolocation) { alert("Geolocation is not supported by your browser."); return; } btn.disabled = true; btn.innerHTML = "…"; navigator.geolocation.getCurrentPosition( ({ coords }) => { const { latitude, longitude } = coords; fetchLocations({ latitude, longitude }).then(renderLocations); btn.innerHTML = "✓"; }, (err) => { console.warn(err); alert( "Sorry, we couldn’t get your location. You can enter an address instead." ); btn.innerHTML = "⚠"; }, { enableHighAccuracy: true, timeout: 10000, maximumAge: 600000 } ); });

5. Allow Search by Location

Some customers may block geolocation or want to search another city. With this step, you’ll let them type a city, postal code, or full address, then geocode it with the Google Maps Geocoding API.

Update your sections/store-finder.liquid section schema to include a Google Maps API Key field:

{% schema %} { "name": "Store Finder", "settings": [ { "type": "text", "id": "storefront_access_token", "label": "Storefront API access token" }, { "type": "text", "id": "google_maps_api_key", "label": "Google Maps API key" } ] } {% endschema %}

Handle form submission by adding an event listener:

async function geocodeAddress(address) { if (!GOOGLE_KEY) throw new Error('Missing Google Maps API key'); const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent( address )}&key=${GOOGLE_KEY}`; const res = await fetch(url); const data = await res.json(); if (data.status !== 'OK' || !data.results.length) { throw new Error('No geocoding results'); } const loc = data.results[0].geometry.location; return { latitude: loc.lat, longitude: loc.lng }; } form.addEventListener('submit', async (e) => { e.preventDefault(); const addr = input.value.trim(); if (!addr) return; try { form.classList.add('is-loading'); const { latitude, longitude } = await geocodeAddress(addr); fetchLocations({ latitude, longitude }).then(renderLocations); } catch (err) { console.warn(err); alert('Sorry, we couldn’t locate that address.'); } finally { form.classList.remove('is-loading'); } });

Limitations

While this expands Shopify’s functionality, it does come with constraints:

  • This approach displays stores set up as ‘locations’ and that offer pickup. There may be scenarios where storing data in metaobjects would be preferable.

  • Our initial load and search functionality rely on JavaScript. If a user has JavaScript disabled, the store finder won’t load.

Summary

Combining Shopify’s Online Store with the Storefront API can enable a ton of flexibility. Be sure to test these solutions thoroughly before deploying and consider edge cases. Each theme will have different features that might need adjusting for a smooth user experience.

To take this to the next step, consider adding store locations as markers on a Google Map using the Google API.

Go further

Subscribe to the newsletter and get a personalized e-book!

No-code solution, no technical knowledge required. AI trained on your e-shop and non-intrusive.

*Unsubscribe anytime. We don't spam.

Subscribe to the newsletter and get a personalized e-book!

No-code solution, no technical knowledge required. AI trained on your e-shop and non-intrusive.

*Unsubscribe anytime. We don't spam.

Subscribe to the newsletter and get a personalized e-book!

No-code solution, no technical knowledge required. AI trained on your e-shop and non-intrusive.

*Unsubscribe anytime. We don't spam.