Select New in the Store Variables section and add the following variables.
Name - Address Auto-complete Google API
Key - google_autocomplete_api_key
Value - your Google Maps API key
Available in Liquid = true
Name - Address Auto-complete Countries
Key - autocomplete_countries
Value - 2 character, comma separated ISO code for each country you want to support i.e. US, CA (Google supports up to five at a time)
Available in Liquid = true
Name - Default Auto-complete Country
Key - default_autocomplete_country
Value - 2 character ISO code for one country you want to include
Available in Liquid = true
Save.
Update your theme layout template (in Liquid)
Open your theme record in StoreConnect.
Open the applicable Theme Template.
If your theme doesn't already have a theme template, grab it here and create one for your theme: theme.liquid
Add the following code to the very bottom of the theme template Content section. This ensures address fields have loaded before the layout runs.
Select Save.
{%- assign google_autocomplete_api_key = store_variables['google_autocomplete_api_key'] | default: blank -%}
{%- assign autocomplete_countries = store_variables['autocomplete_countries'] | default: 'AU,US' | strip -%}
{%- assign default_autocomplete_country = store_variables['default_autocomplete_country'] | default: 'AU' -%}
// Google Maps loader
(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})
({key: "{{ google_autocomplete_api_key }}", v: "weekly"});
const allowedCountries = '{{ autocomplete_countries }}'.split(',').map(c => c.trim()).filter(c => c);
let autocomplete;
let address1Field;
let address2Field;
let cityField;
let postalField;
let countryField;
let stateField;
async function initAutocomplete() {
try {
// Import the places libraryawait google.maps.importLibrary('places');
// Get form fields
address1Field = document.querySelector('#shipping_address_line_1');
address2Field = document.querySelector('#shipping_address_line_2');
cityField = document.querySelector('#shipping_city__customer_information');
postalField = document.querySelector('#shipping_postal_code__customer_information');
countryField = document.querySelector('#shipping_country__customer_information');
stateField = document.querySelector('#shipping_state__customer_information');
if (!address1Field) {
console.error('Address field not found');
return;
}
// Set default countryif (countryField) {
countryField.value = '{{ default_autocomplete_country }}';
countryField.dispatchEvent(new Event('change', { bubbles: true }));
}
// Update placeholder
address1Field.placeholder = 'Start typing your address...';
// Create autocomplete on the existing input using the Autocomplete class
autocomplete = new google.maps.places.Autocomplete(address1Field, {
componentRestrictions: { country: ['{{ default_autocomplete_country }}'] },
fields: ['address_components', 'geometry'],
types: ['address']
});
// Listen for place selection
autocomplete.addListener('place_changed', fillInAddress);
// Listen for country changesif (countryField) {
countryField.addEventListener('change', function() {
const selectedCountry = this.value;
if (selectedCountry && allowedCountries.includes(selectedCountry)) {
// Update autocomplete country restriction
autocomplete.setComponentRestrictions({
country: [selectedCountry]
});
// Clear form fields when country changesif (address1Field) address1Field.value = '';
if (address2Field) address2Field.value = '';
if (cityField) cityField.value = '';
if (postalField) postalField.value = '';
if (stateField) stateField.value = '';
}
});
}
} catch (error) {
console.error('Failed to initialize autocomplete:', error);
}
}
function fillInAddress() {
const place = autocomplete.getPlace();
if (!place.address_components) {
console.log('No address components found');
return;
}
let address1 = '';
let postcode = '';
let city = '';
let state = '';
// Parse address componentsfor (const component of place.address_components) {
const types = component.types;
if (types.includes('street_number')) {
address1 = component.long_name + ' ';
}
if (types.includes('route')) {
address1 += component.long_name;
}
if (types.includes('locality')) {
city = component.long_name;
}
if (types.includes('administrative_area_level_1')) {
state = component.short_name;
}
if (types.includes('postal_code')) {
postcode = component.long_name;
}
}
// Fill in the form fields
address1Field.value = address1.trim();
if (cityField) cityField.value = city;
if (postalField) postalField.value = postcode;
// Set state - give state dropdown time to populateif (stateField && state) {
setTimeout(() => {
stateField.value = state;
stateField.dispatchEvent(new Event('change', { bubbles: true }));
}, 500);
}
// Focus on address line 2 for apartment/unit numberif (address2Field) {
address2Field.focus();
}
console.log('Address filled:', { address1, city, state, postcode });
}
// Initialize when DOM is readyif (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initAutocomplete);
} else {
initAutocomplete();
}
/* Style the autocomplete dropdown */
.pac-container {
z-index: 10000 !important;
border-radius: 8px;
margin-top: 4px;
box-shadow: 02px 6px rgba(0,0,0,0.3);
font-family: inherit;
}
.pac-item {
padding: 10px 12px;
cursor: pointer;
font-size: 14px;
line-height: 1.4;
}
.pac-item:hover {
background-color: #f5f5f5;
}
.pac-item-query {
font-size: 15px;
color: #333;
}
.pac-matched {
font-weight: 600;
}