Nov 29, 2018 3:01 PM
I am creating a simple registration form containing email, first name, last name, phone number, and street address fields. For the street address, I would like to add real time autocomplete/lookup functionality, using the smartystreets API documented here: https://smartystreets.com/docs/plugins/website.
I'm new to hubspot development, and not really sure where to start with this, or how it would be structured.
Solved! Go to Solution.
Mar 24, 2023 5:40 PM
I know this is an old thread but I stumbled across it when I was trying to figure this out so I figured I'd share my solution:
This can be done with Google Maps API. I'll do my best to explain it below, but you can also hire my agency to do it for you if you'd rather not bother with it (SimpleStrat.com).
To do this, you'll need to add the javascript below to the page where your form will appear. There's also a way to add it to the form embed code directly but I won't get into that here. There are a couple things you'll likely need to edit depending on your form and the fields you want filled in.
This code is currently set up to work for US and Canada is based on the code found on this page: https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete-addressform
// Create the script tag, set the appropriate attributes, be sure to replace "YOURAPIKEY" in the script source url with your Google Maps API Key
var script = document.createElement('script');
script.src='https://maps.googleapis.com/maps/api/js?key=YOURAPIKEY&libraries=places&callback=initAutocomplete';
script.async = true;
// Append the 'script' element to 'head' after the page loads
window.addEventListener('load', function(){document.head.appendChild(script);})
let autocomplete;
let address1Field;
let address2Field;
let postalField;
function initAutocomplete() {
//change .hs-address below to the class for your 1st line
//address input field if necessary
address1Field = document.querySelector(".hs-address .hs-input");
//change .hs-zip below to the class for your postal code input field if necessary
postalField = document.querySelector(".hs-zip .hs-input");
// Create the autocomplete object, restricting the search predictions to
// addresses in the US and Canada.
autocomplete = new google.maps.places.Autocomplete(address1Field, {
componentRestrictions: { country: ["us", "ca"] },
fields: ["address_components", "geometry"],
types: ["address"],
});
// When the user selects an address from the drop-down, populate the address fields in the form.
autocomplete.addListener("place_changed", fillInAddress);
}
function fillInAddress() {
// Get the place details from the autocomplete object.
const place = autocomplete.getPlace();
let address1 = "";
let postcode = "";
// Get each component of the address from the place details,
// and then fill-in the corresponding field on the form.
// place.address_components are google.maps.GeocoderAddressComponent objects
// which are documented at http://goo.gle/3l5i5Mr
for (const component of place.address_components) {
// @TS-ignore remove once typings fixed
const componentType = component.types[0];
switch (componentType) {
case "street_number": {
address1 = `${component.long_name} ${address1}`;
break;
}
case "route": {
address1 += component.short_name;
break;
}
case "postal_code": {
postcode = `${component.long_name}${postcode}`;
break;
}
case "postal_code_suffix": {
postcode = `${postcode}-${component.long_name}`;
break;
}
case "locality":
//change .hs-city below to the class for your city input field if necessary
//comment out or delete the line below if no city field in your form
document.querySelector(".hs-city .hs-input").value = component.long_name;
break;
case "administrative_area_level_1": {
//change .hs-state below to the class for your city input field if necessary
//comment out or delete the line below if no state field in your form
document.querySelector(".hs-state .hs-input").value = component.short_name;
break;
}
case "country":
//change .hs-country below to the class for your city input field if necessary
//comment out or delete the line below if no country field in your form
document.querySelector(".hs-country .hs-input").value = component.long_name;
break;
}
}
address1Field.value = address1;
//comment out or delete the line below if no postal code field in form
postalField.value = postcode;
}
window.initAutocomplete = initAutocomplete;
Feb 25, 2025 11:16 AM
Yes! This is very possible with Smarty (formerly SmartyStreets) in Hubspot. You'll create a custom HTML form that communicates to Hubspot through the form API.
It sounds complicated, and it kind of is, but not really.
Smarty released a tutorial on it last year: https://www.smarty.com/articles/hubspot-address-autocomplete-guide
Basically, you create an HTML form, link it to the Hubspot form API, and then connect it to Smarty's autocomplete functionality. Pretty simple.
Here's basic code for an HTML form that is prepared for autocomplete:
<form id="myCoolForm" style="padding: 30px; border: 1px solid #476884; border-radius: 16px;"><input id="myCoolFirstname" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="First name *"> <input id="myCoolLastname" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="Last name *"> <input id="myCoolEmail" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" required="" type="text" placeholder="Email *"> <input id="myCoolAddress" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="Address *"> <input id="myCoolCity" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="City *"><select id="myCoolState" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;">
<option value="AL">AL</option>
<option value="AK">AK</option>
<option value="AZ">AZ</option>
<option value="AR">AR</option>
<option value="CA">CA</option>
<option value="CO">CO</option>
<option value="CT">CT</option>
<option value="DE">DE</option>
<option value="FL">FL</option>
<option value="GA">GA</option>
<option value="HI">HI</option>
<option value="ID">ID</option>
<option value="IL">IL</option>
<option value="IN">IN</option>
<option value="IA">IA</option>
<option value="KS">KS</option>
<option value="KY">KY</option>
<option value="LA">LA</option>
<option value="ME">ME</option>
<option value="MD">MD</option>
<option value="MA">MA</option>
<option value="MI">MI</option>
<option value="MN">MN</option>
<option value="MS">MS</option>
<option value="MO">MO</option>
<option value="MT">MT</option>
<option value="NE">NE</option>
<option value="NV">NV</option>
<option value="NH">NH</option>
<option value="NJ">NJ</option>
<option value="NM">NM</option>
<option value="NY">NY</option>
<option value="NC">NC</option>
<option value="ND">ND</option>
<option value="OH">OH</option>
<option value="OK">OK</option>
<option value="OR">OR</option>
<option value="PA">PA</option>
<option value="RI">RI</option>
<option value="SC">SC</option>
<option value="SD">SD</option>
<option value="TN">TN</option>
<option value="TX">TX</option>
<option value="UT">UT</option>
<option value="VT">VT</option>
<option value="VA">VA</option>
<option value="WA">WA</option>
<option value="WV">WV</option>
<option value="WI">WI</option>
<option value="WY">WY</option>
</select><input id="myCoolZip" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="ZIP Code">
<div style="padding-bottom: 15px; padding-left: 15px;">Your <a href="https://YOUR PRIVACY POLICY LINK GOES HERE">privacy</a> is important to us. You agree to receive messages regarding our products and services by submitting this form. You may unsubscribe at any time.</div>
<div style="text-align: center;"><button id="myCoolButton" style="background-color: #0066ff; color: white; padding: 15px 40px; font-size: 20px; font-weight: bold; border: none; border-radius: 90px; cursor: pointer; display: inline-block; text-align: center; text-decoration: none;">SUBMIT</button></div>
</form>
And then whatever page you're putting it on just needs to have this JavaScript in the footer.
<script>
// HubSpot Settings - change stuff between the quotes after the equals sign (keep the quotes).
const MY_COOL_HUBSPOT_FORM_ID = 'YOUR HUBSPOT FORM ID';
const MY_COOL_HUBSPOT_PORTAL_ID = 'YOUR HUBSPOT PORTAL ID';
const MY_COOL_CONFIRMATION_PAGE = 'YOUR CONFIRMATION PAGE URL BEGINNING WITH https://';
const MY_COOL_ERROR_MESSAGE = 'All fields are required.';
// Smarty Settings - change stuff between the quotes after the equals sign (keep the quotes).
const MY_COOL_SMARTY_EMBEDDED_KEY = 'YOUR SMARTY EMBEDDED KEY GOES HERE';
const MY_COOL_SMARTY_SOURCE = 'postal'; // valid options are 'postal' or 'all'
// ------ DON'T MODIFY BELOW THIS LINE ------
document.getElementById('myCoolButton').addEventListener('click', (e) => {
e.preventDefault();
submitData();
});
async function submitData() {
const url = `https://api.hsforms.com/submissions/v3/integration/submit/${MY_COOL_HUBSPOT_PORTAL_ID}/${MY_COOL_HUBSPOT_FORM_ID}`;
const headers = {
'accept': 'application/json',
'content-type': 'application/json',
};
const data = {
"fields": [{
"name": "firstname",
"value": document.getElementById('myCoolFirstname').value
},
{
"name": "lastname",
"value": document.getElementById('myCoolLastname').value
},
{
"name": "email",
"value": document.getElementById('myCoolEmail').value
},
{
"name": "address",
"value": document.getElementById('myCoolAddress').value
},
{
"name": "city",
"value": document.getElementById('myCoolCity').value
},
{
"name": "state",
"value": document.getElementById('myCoolState').value
},
{
"name": "zip",
"value": document.getElementById('myCoolZip').value
}
],
"skipValidation": false,
"context": {}
};
try {
const response = await fetch(url, {
method: 'POST',
headers: headers,
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
console.log(responseData);
window.location.href = MY_COOL_CONFIRMATION_PAGE;
} catch (error) {
console.error('Error:', error);
alert(MY_COOL_ERROR_MESSAGE);
}
}
((global) => {
let settings = {
embeddedKey: '',
addressId: 'address',
cityId: 'city',
stateId: 'state',
zipCodeId: 'zip',
styleBackgroundColorHexString: '#fff',
styleColorHexString: '#333',
styleHoverBackgroundColorHexString: '#cfe8ff',
styleHoverColorHexString: '#000',
styleBorderColorHexString: '#e0e0e0',
styleBorderPixelWidthInt: 2,
styleBorderRadiusInt: 8,
styleFontFamilyString: 'sans-serif',
styleFontSizePixelInt: 14,
styleRowPaddingString: '8px',
styleBoxPixelWidthInt: 300,
styleBoxPixelHeightInt: 300,
styleSelectedSuggestionColorHexString: '#000',
styleSelectedSuggestionBackgroundColorHexString: '#ddd',
suggestionElement: document.createElement('div'),
suggestionId: null,
offsetHeight: 20,
addressElement: null,
activeStyles: '',
inactiveStyles: 'display: none;',
selectedIndex: 0,
lastAction: '',
};
const wrapperStyles = `
display: inline-block;
position: relative;
width: 100%;
`;
const extendSettings = (defaults, options) => {
// @todo ensure the options are allowed.
return {
...defaults,
...options
};
};
const wrapElementsWithDiv = (elementId) => {
const knownElement = document.getElementById(elementId);
if (!knownElement) {
console.error(`Element with ID ${elementId} not found.`);
return;
}
settings.addressElement = knownElement;
settings.offsetHeight = knownElement.offsetHeight;
const wrapperDiv = document.createElement('div');
wrapperDiv.style.cssText = wrapperStyles;
knownElement.parentNode.insertBefore(wrapperDiv, knownElement);
wrapperDiv.appendChild(knownElement);
knownElement.parentNode.insertBefore(settings.suggestionElement, knownElement.nextSibling);
}
const sendLookupRequest = async (searchValue, selected = "") => {
const params = new URLSearchParams({
key: settings.embeddedKey,
search: searchValue,
source: MY_COOL_SMARTY_SOURCE,
selected
});
try {
const request = await fetch(
`https://us-autocomplete-pro.api.smarty.com/lookup?${params}`
);
const data = await request.json();
if (data?.suggestions?.length > 0) formatSuggestions(data.suggestions);
} catch (e) {
console.error(e.message);
}
};
const applyStyles = (element, styles) => {
for (let property in styles) {
element.style[property] = styles[property];
}
};
const formatSuggestions = (suggestions) => {
const {
suggestionElement,
inactiveStyles,
styleRowPaddingString,
styleHoverBackgroundColorHexString,
styleHoverColorHexString,
styleBackgroundColorHexString,
styleColorHexString,
styleFontSizePixelInt,
styleSelectedSuggestionColorHexString,
styleSelectedSuggestionBackgroundColorHexString,
activeStyles,
} = settings;
suggestionElement.innerHTML = '';
suggestionElement.style.cssText = activeStyles;
const formattedSuggestions = suggestions.map((suggestion, index) => {
const divElement = document.createElement("div");
divElement.classList.add('smarty-suggestion');
divElement.style['padding'] = styleRowPaddingString;
divElement.style['fontSize'] = `${styleFontSizePixelInt}px`;
if (index === 0) {
applyStyles(divElement, {
color: styleSelectedSuggestionColorHexString,
backgroundColor: styleSelectedSuggestionBackgroundColorHexString,
});
settings.selectedIndex = 0;
}
const {
street_line,
city,
state,
zipcode,
secondary,
entries
} = suggestion;
const hasSecondaryData = secondary && entries > 1;
divElement.innerText = `${street_line} ${secondary} ${
hasSecondaryData ? `(${entries} entries)` : ""
} ${city} ${state} ${zipcode}`;
divElement.addEventListener('mouseover', () => {
if (settings.lastAction === 'keyboard') return;
applyStyles(divElement, {
backgroundColor: styleHoverBackgroundColorHexString,
color: styleHoverColorHexString,
});
});
divElement.addEventListener('mouseout', () => {
applyStyles(divElement, {
backgroundColor: styleBackgroundColorHexString,
color: styleColorHexString,
});
});
divElement.addEventListener("click", async () => {
const streetLineWithSecondary = `${street_line} ${secondary}`.trim();
if (hasSecondaryData) {
const selected = `${streetLineWithSecondary} (${entries}) ${city} ${state} ${zipcode}`;
await sendLookupRequest(streetLineWithSecondary, selected);
} else {
suggestionElement.style.cssText = inactiveStyles;
}
populateForm({
streetLineWithSecondary,
city,
state,
zipcode
});
});
return divElement;
});
suggestionElement.append(...formattedSuggestions);
}
const populateForm = ({
streetLineWithSecondary,
city,
state,
zipcode
}) => {
const {
addressId,
cityId,
stateId,
zipCodeId
} = settings;
document.getElementById(addressId).value = streetLineWithSecondary;
document.getElementById(cityId).value = city;
document.getElementById(stateId).value = state;
document.getElementById(zipCodeId).value = zipcode;
};
const scrollWrapperToSelected = () => {
const {
selectedIndex,
suggestionElement
} = settings;
const elements = document.getElementsByClassName('smarty-suggestion');
if (selectedIndex >= 0 && selectedIndex < elements.length) {
const selectedChild = elements[selectedIndex];
const wrapperRect = suggestionElement.getBoundingClientRect();
const selectedRect = selectedChild.getBoundingClientRect();
// Check if selected child is above the viewport
if (selectedRect.top < wrapperRect.top) {
suggestionElement.scrollTop -= (wrapperRect.top - selectedRect.top);
}
// Check if selected child is below the viewport
else if (selectedRect.bottom > wrapperRect.bottom) {
suggestionElement.scrollTop += (selectedRect.bottom - wrapperRect.bottom);
}
}
}
LikeButtaSmartyUsAddressAutocomplete = (userSettings) => {
settings = extendSettings(settings, userSettings);
wrapElementsWithDiv(settings.addressId);
settings.activeStyles = `
display: block;
position: absolute;
overflow-y: auto;
cursor: pointer;
top: ${settings.offsetHeight};
width: ${settings.styleBoxPixelWidthInt}px;
height: ${settings.styleBoxPixelHeightInt}px;
border: solid ${settings.styleBorderPixelWidthInt}px ${settings.styleBorderColorHexString};
border-radius: ${settings.styleBorderRadiusInt}px;
background-color: ${settings.styleBackgroundColorHexString};
font-family: ${settings.styleFontFamilyString};
color: ${settings.styleColorHexString};
`;
settings.suggestionElement.id = 'smartySuggestionBox';
settings.suggestionId = settings.suggestionElement.id;
const {
addressElement,
suggestionElement,
inactiveStyles,
activeStyles
} = settings;
suggestionElement.style.cssText = inactiveStyles;
addressElement.addEventListener("keyup", (e) => {
if ([
'Shift', 'Control', 'Alt', 'Meta', 'CapsLock', 'Tab', 'ArrowUp', 'ArrowDown',
'ArrowLeft', 'ArrowRight', 'Escape', 'F1', 'F2', 'F3', 'F4', 'F5',
'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12'
].includes(e.key)) return;
if (e.key === 'Enter') {
const elements = document.getElementsByClassName('smarty-suggestion');
elements[settings.selectedIndex].click();
return;
}
const searchValue = e.target.value;
if (!searchValue) {
suggestionElement.style.cssText = inactiveStyles;
return;
}
sendLookupRequest(searchValue);
});
addressElement.addEventListener("keydown", async (e) => {
if (!['ArrowUp', 'ArrowDown'].includes(e.key)) return;
e.preventDefault();
const {
selectedIndex,
styleColorHexString,
styleBackgroundColorHexString,
styleSelectedSuggestionColorHexString,
styleSelectedSuggestionBackgroundColorHexString,
} = settings;
const elements = document.getElementsByClassName('smarty-suggestion');
[...elements].forEach((element) => {
applyStyles(element, {
color: styleColorHexString,
backgroundColor: styleBackgroundColorHexString,
});
});
if (elements.length === selectedIndex + 1 && e.key === 'ArrowDown') {
settings.selectedIndex = 0;
} else if (selectedIndex === 0 && e.key === 'ArrowUp') {
settings.selectedIndex = elements.length - 1;
} else if (e.key === 'ArrowDown') {
settings.selectedIndex += 1;
} else {
settings.selectedIndex -= 1;
}
applyStyles(elements[settings.selectedIndex], {
color: styleSelectedSuggestionColorHexString,
backgroundColor: styleSelectedSuggestionBackgroundColorHexString,
});
settings.lastAction = 'keyboard';
scrollWrapperToSelected();
});
suggestionElement.addEventListener('mousemove', (e) => {
settings.lastAction = 'mouse';
});
suggestionElement.addEventListener('mouseout', (e) => {
const elements = document.getElementsByClassName('smarty-suggestion');
const {
selectedIndex,
styleSelectedSuggestionColorHexString,
styleSelectedSuggestionBackgroundColorHexString
} = settings;
applyStyles(elements[selectedIndex], {
color: styleSelectedSuggestionColorHexString,
backgroundColor: styleSelectedSuggestionBackgroundColorHexString,
});
});
document.addEventListener('click', (e) => {
if (!suggestionElement.contains(e.target) && !e.target.classList.contains('smarty-suggestion')) {
suggestionElement.style.cssText = inactiveStyles;
}
});
};
global.LikeButtaSmartyUsAddressAutocomplete = LikeButtaSmartyUsAddressAutocomplete;
})(window);
LikeButtaSmartyUsAddressAutocomplete({
embeddedKey: MY_COOL_SMARTY_EMBEDDED_KEY,
addressId: 'myCoolAddress',
cityId: 'myCoolCity',
stateId: 'myCoolState',
zipCodeId: 'myCoolZip',
});
</script>
I got it working really fast, in like ten minutes.
Feb 25, 2025 1:40 PM
I think this would solve your problem: https://www.smarty.com/articles/hubspot-address-autocomplete-guide
Feb 25, 2025 11:16 AM
Yes! This is very possible with Smarty (formerly SmartyStreets) in Hubspot. You'll create a custom HTML form that communicates to Hubspot through the form API.
It sounds complicated, and it kind of is, but not really.
Smarty released a tutorial on it last year: https://www.smarty.com/articles/hubspot-address-autocomplete-guide
Basically, you create an HTML form, link it to the Hubspot form API, and then connect it to Smarty's autocomplete functionality. Pretty simple.
Here's basic code for an HTML form that is prepared for autocomplete:
<form id="myCoolForm" style="padding: 30px; border: 1px solid #476884; border-radius: 16px;"><input id="myCoolFirstname" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="First name *"> <input id="myCoolLastname" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="Last name *"> <input id="myCoolEmail" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" required="" type="text" placeholder="Email *"> <input id="myCoolAddress" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="Address *"> <input id="myCoolCity" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="City *"><select id="myCoolState" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;">
<option value="AL">AL</option>
<option value="AK">AK</option>
<option value="AZ">AZ</option>
<option value="AR">AR</option>
<option value="CA">CA</option>
<option value="CO">CO</option>
<option value="CT">CT</option>
<option value="DE">DE</option>
<option value="FL">FL</option>
<option value="GA">GA</option>
<option value="HI">HI</option>
<option value="ID">ID</option>
<option value="IL">IL</option>
<option value="IN">IN</option>
<option value="IA">IA</option>
<option value="KS">KS</option>
<option value="KY">KY</option>
<option value="LA">LA</option>
<option value="ME">ME</option>
<option value="MD">MD</option>
<option value="MA">MA</option>
<option value="MI">MI</option>
<option value="MN">MN</option>
<option value="MS">MS</option>
<option value="MO">MO</option>
<option value="MT">MT</option>
<option value="NE">NE</option>
<option value="NV">NV</option>
<option value="NH">NH</option>
<option value="NJ">NJ</option>
<option value="NM">NM</option>
<option value="NY">NY</option>
<option value="NC">NC</option>
<option value="ND">ND</option>
<option value="OH">OH</option>
<option value="OK">OK</option>
<option value="OR">OR</option>
<option value="PA">PA</option>
<option value="RI">RI</option>
<option value="SC">SC</option>
<option value="SD">SD</option>
<option value="TN">TN</option>
<option value="TX">TX</option>
<option value="UT">UT</option>
<option value="VT">VT</option>
<option value="VA">VA</option>
<option value="WA">WA</option>
<option value="WV">WV</option>
<option value="WI">WI</option>
<option value="WY">WY</option>
</select><input id="myCoolZip" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="ZIP Code">
<div style="padding-bottom: 15px; padding-left: 15px;">Your <a href="https://YOUR PRIVACY POLICY LINK GOES HERE">privacy</a> is important to us. You agree to receive messages regarding our products and services by submitting this form. You may unsubscribe at any time.</div>
<div style="text-align: center;"><button id="myCoolButton" style="background-color: #0066ff; color: white; padding: 15px 40px; font-size: 20px; font-weight: bold; border: none; border-radius: 90px; cursor: pointer; display: inline-block; text-align: center; text-decoration: none;">SUBMIT</button></div>
</form>
And then whatever page you're putting it on just needs to have this JavaScript in the footer.
<script>
// HubSpot Settings - change stuff between the quotes after the equals sign (keep the quotes).
const MY_COOL_HUBSPOT_FORM_ID = 'YOUR HUBSPOT FORM ID';
const MY_COOL_HUBSPOT_PORTAL_ID = 'YOUR HUBSPOT PORTAL ID';
const MY_COOL_CONFIRMATION_PAGE = 'YOUR CONFIRMATION PAGE URL BEGINNING WITH https://';
const MY_COOL_ERROR_MESSAGE = 'All fields are required.';
// Smarty Settings - change stuff between the quotes after the equals sign (keep the quotes).
const MY_COOL_SMARTY_EMBEDDED_KEY = 'YOUR SMARTY EMBEDDED KEY GOES HERE';
const MY_COOL_SMARTY_SOURCE = 'postal'; // valid options are 'postal' or 'all'
// ------ DON'T MODIFY BELOW THIS LINE ------
document.getElementById('myCoolButton').addEventListener('click', (e) => {
e.preventDefault();
submitData();
});
async function submitData() {
const url = `https://api.hsforms.com/submissions/v3/integration/submit/${MY_COOL_HUBSPOT_PORTAL_ID}/${MY_COOL_HUBSPOT_FORM_ID}`;
const headers = {
'accept': 'application/json',
'content-type': 'application/json',
};
const data = {
"fields": [{
"name": "firstname",
"value": document.getElementById('myCoolFirstname').value
},
{
"name": "lastname",
"value": document.getElementById('myCoolLastname').value
},
{
"name": "email",
"value": document.getElementById('myCoolEmail').value
},
{
"name": "address",
"value": document.getElementById('myCoolAddress').value
},
{
"name": "city",
"value": document.getElementById('myCoolCity').value
},
{
"name": "state",
"value": document.getElementById('myCoolState').value
},
{
"name": "zip",
"value": document.getElementById('myCoolZip').value
}
],
"skipValidation": false,
"context": {}
};
try {
const response = await fetch(url, {
method: 'POST',
headers: headers,
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
console.log(responseData);
window.location.href = MY_COOL_CONFIRMATION_PAGE;
} catch (error) {
console.error('Error:', error);
alert(MY_COOL_ERROR_MESSAGE);
}
}
((global) => {
let settings = {
embeddedKey: '',
addressId: 'address',
cityId: 'city',
stateId: 'state',
zipCodeId: 'zip',
styleBackgroundColorHexString: '#fff',
styleColorHexString: '#333',
styleHoverBackgroundColorHexString: '#cfe8ff',
styleHoverColorHexString: '#000',
styleBorderColorHexString: '#e0e0e0',
styleBorderPixelWidthInt: 2,
styleBorderRadiusInt: 8,
styleFontFamilyString: 'sans-serif',
styleFontSizePixelInt: 14,
styleRowPaddingString: '8px',
styleBoxPixelWidthInt: 300,
styleBoxPixelHeightInt: 300,
styleSelectedSuggestionColorHexString: '#000',
styleSelectedSuggestionBackgroundColorHexString: '#ddd',
suggestionElement: document.createElement('div'),
suggestionId: null,
offsetHeight: 20,
addressElement: null,
activeStyles: '',
inactiveStyles: 'display: none;',
selectedIndex: 0,
lastAction: '',
};
const wrapperStyles = `
display: inline-block;
position: relative;
width: 100%;
`;
const extendSettings = (defaults, options) => {
// @todo ensure the options are allowed.
return {
...defaults,
...options
};
};
const wrapElementsWithDiv = (elementId) => {
const knownElement = document.getElementById(elementId);
if (!knownElement) {
console.error(`Element with ID ${elementId} not found.`);
return;
}
settings.addressElement = knownElement;
settings.offsetHeight = knownElement.offsetHeight;
const wrapperDiv = document.createElement('div');
wrapperDiv.style.cssText = wrapperStyles;
knownElement.parentNode.insertBefore(wrapperDiv, knownElement);
wrapperDiv.appendChild(knownElement);
knownElement.parentNode.insertBefore(settings.suggestionElement, knownElement.nextSibling);
}
const sendLookupRequest = async (searchValue, selected = "") => {
const params = new URLSearchParams({
key: settings.embeddedKey,
search: searchValue,
source: MY_COOL_SMARTY_SOURCE,
selected
});
try {
const request = await fetch(
`https://us-autocomplete-pro.api.smarty.com/lookup?${params}`
);
const data = await request.json();
if (data?.suggestions?.length > 0) formatSuggestions(data.suggestions);
} catch (e) {
console.error(e.message);
}
};
const applyStyles = (element, styles) => {
for (let property in styles) {
element.style[property] = styles[property];
}
};
const formatSuggestions = (suggestions) => {
const {
suggestionElement,
inactiveStyles,
styleRowPaddingString,
styleHoverBackgroundColorHexString,
styleHoverColorHexString,
styleBackgroundColorHexString,
styleColorHexString,
styleFontSizePixelInt,
styleSelectedSuggestionColorHexString,
styleSelectedSuggestionBackgroundColorHexString,
activeStyles,
} = settings;
suggestionElement.innerHTML = '';
suggestionElement.style.cssText = activeStyles;
const formattedSuggestions = suggestions.map((suggestion, index) => {
const divElement = document.createElement("div");
divElement.classList.add('smarty-suggestion');
divElement.style['padding'] = styleRowPaddingString;
divElement.style['fontSize'] = `${styleFontSizePixelInt}px`;
if (index === 0) {
applyStyles(divElement, {
color: styleSelectedSuggestionColorHexString,
backgroundColor: styleSelectedSuggestionBackgroundColorHexString,
});
settings.selectedIndex = 0;
}
const {
street_line,
city,
state,
zipcode,
secondary,
entries
} = suggestion;
const hasSecondaryData = secondary && entries > 1;
divElement.innerText = `${street_line} ${secondary} ${
hasSecondaryData ? `(${entries} entries)` : ""
} ${city} ${state} ${zipcode}`;
divElement.addEventListener('mouseover', () => {
if (settings.lastAction === 'keyboard') return;
applyStyles(divElement, {
backgroundColor: styleHoverBackgroundColorHexString,
color: styleHoverColorHexString,
});
});
divElement.addEventListener('mouseout', () => {
applyStyles(divElement, {
backgroundColor: styleBackgroundColorHexString,
color: styleColorHexString,
});
});
divElement.addEventListener("click", async () => {
const streetLineWithSecondary = `${street_line} ${secondary}`.trim();
if (hasSecondaryData) {
const selected = `${streetLineWithSecondary} (${entries}) ${city} ${state} ${zipcode}`;
await sendLookupRequest(streetLineWithSecondary, selected);
} else {
suggestionElement.style.cssText = inactiveStyles;
}
populateForm({
streetLineWithSecondary,
city,
state,
zipcode
});
});
return divElement;
});
suggestionElement.append(...formattedSuggestions);
}
const populateForm = ({
streetLineWithSecondary,
city,
state,
zipcode
}) => {
const {
addressId,
cityId,
stateId,
zipCodeId
} = settings;
document.getElementById(addressId).value = streetLineWithSecondary;
document.getElementById(cityId).value = city;
document.getElementById(stateId).value = state;
document.getElementById(zipCodeId).value = zipcode;
};
const scrollWrapperToSelected = () => {
const {
selectedIndex,
suggestionElement
} = settings;
const elements = document.getElementsByClassName('smarty-suggestion');
if (selectedIndex >= 0 && selectedIndex < elements.length) {
const selectedChild = elements[selectedIndex];
const wrapperRect = suggestionElement.getBoundingClientRect();
const selectedRect = selectedChild.getBoundingClientRect();
// Check if selected child is above the viewport
if (selectedRect.top < wrapperRect.top) {
suggestionElement.scrollTop -= (wrapperRect.top - selectedRect.top);
}
// Check if selected child is below the viewport
else if (selectedRect.bottom > wrapperRect.bottom) {
suggestionElement.scrollTop += (selectedRect.bottom - wrapperRect.bottom);
}
}
}
LikeButtaSmartyUsAddressAutocomplete = (userSettings) => {
settings = extendSettings(settings, userSettings);
wrapElementsWithDiv(settings.addressId);
settings.activeStyles = `
display: block;
position: absolute;
overflow-y: auto;
cursor: pointer;
top: ${settings.offsetHeight};
width: ${settings.styleBoxPixelWidthInt}px;
height: ${settings.styleBoxPixelHeightInt}px;
border: solid ${settings.styleBorderPixelWidthInt}px ${settings.styleBorderColorHexString};
border-radius: ${settings.styleBorderRadiusInt}px;
background-color: ${settings.styleBackgroundColorHexString};
font-family: ${settings.styleFontFamilyString};
color: ${settings.styleColorHexString};
`;
settings.suggestionElement.id = 'smartySuggestionBox';
settings.suggestionId = settings.suggestionElement.id;
const {
addressElement,
suggestionElement,
inactiveStyles,
activeStyles
} = settings;
suggestionElement.style.cssText = inactiveStyles;
addressElement.addEventListener("keyup", (e) => {
if ([
'Shift', 'Control', 'Alt', 'Meta', 'CapsLock', 'Tab', 'ArrowUp', 'ArrowDown',
'ArrowLeft', 'ArrowRight', 'Escape', 'F1', 'F2', 'F3', 'F4', 'F5',
'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12'
].includes(e.key)) return;
if (e.key === 'Enter') {
const elements = document.getElementsByClassName('smarty-suggestion');
elements[settings.selectedIndex].click();
return;
}
const searchValue = e.target.value;
if (!searchValue) {
suggestionElement.style.cssText = inactiveStyles;
return;
}
sendLookupRequest(searchValue);
});
addressElement.addEventListener("keydown", async (e) => {
if (!['ArrowUp', 'ArrowDown'].includes(e.key)) return;
e.preventDefault();
const {
selectedIndex,
styleColorHexString,
styleBackgroundColorHexString,
styleSelectedSuggestionColorHexString,
styleSelectedSuggestionBackgroundColorHexString,
} = settings;
const elements = document.getElementsByClassName('smarty-suggestion');
[...elements].forEach((element) => {
applyStyles(element, {
color: styleColorHexString,
backgroundColor: styleBackgroundColorHexString,
});
});
if (elements.length === selectedIndex + 1 && e.key === 'ArrowDown') {
settings.selectedIndex = 0;
} else if (selectedIndex === 0 && e.key === 'ArrowUp') {
settings.selectedIndex = elements.length - 1;
} else if (e.key === 'ArrowDown') {
settings.selectedIndex += 1;
} else {
settings.selectedIndex -= 1;
}
applyStyles(elements[settings.selectedIndex], {
color: styleSelectedSuggestionColorHexString,
backgroundColor: styleSelectedSuggestionBackgroundColorHexString,
});
settings.lastAction = 'keyboard';
scrollWrapperToSelected();
});
suggestionElement.addEventListener('mousemove', (e) => {
settings.lastAction = 'mouse';
});
suggestionElement.addEventListener('mouseout', (e) => {
const elements = document.getElementsByClassName('smarty-suggestion');
const {
selectedIndex,
styleSelectedSuggestionColorHexString,
styleSelectedSuggestionBackgroundColorHexString
} = settings;
applyStyles(elements[selectedIndex], {
color: styleSelectedSuggestionColorHexString,
backgroundColor: styleSelectedSuggestionBackgroundColorHexString,
});
});
document.addEventListener('click', (e) => {
if (!suggestionElement.contains(e.target) && !e.target.classList.contains('smarty-suggestion')) {
suggestionElement.style.cssText = inactiveStyles;
}
});
};
global.LikeButtaSmartyUsAddressAutocomplete = LikeButtaSmartyUsAddressAutocomplete;
})(window);
LikeButtaSmartyUsAddressAutocomplete({
embeddedKey: MY_COOL_SMARTY_EMBEDDED_KEY,
addressId: 'myCoolAddress',
cityId: 'myCoolCity',
stateId: 'myCoolState',
zipCodeId: 'myCoolZip',
});
</script>
I got it working really fast, in like ten minutes.
Mar 24, 2023 5:40 PM
I know this is an old thread but I stumbled across it when I was trying to figure this out so I figured I'd share my solution:
This can be done with Google Maps API. I'll do my best to explain it below, but you can also hire my agency to do it for you if you'd rather not bother with it (SimpleStrat.com).
To do this, you'll need to add the javascript below to the page where your form will appear. There's also a way to add it to the form embed code directly but I won't get into that here. There are a couple things you'll likely need to edit depending on your form and the fields you want filled in.
This code is currently set up to work for US and Canada is based on the code found on this page: https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete-addressform
// Create the script tag, set the appropriate attributes, be sure to replace "YOURAPIKEY" in the script source url with your Google Maps API Key
var script = document.createElement('script');
script.src='https://maps.googleapis.com/maps/api/js?key=YOURAPIKEY&libraries=places&callback=initAutocomplete';
script.async = true;
// Append the 'script' element to 'head' after the page loads
window.addEventListener('load', function(){document.head.appendChild(script);})
let autocomplete;
let address1Field;
let address2Field;
let postalField;
function initAutocomplete() {
//change .hs-address below to the class for your 1st line
//address input field if necessary
address1Field = document.querySelector(".hs-address .hs-input");
//change .hs-zip below to the class for your postal code input field if necessary
postalField = document.querySelector(".hs-zip .hs-input");
// Create the autocomplete object, restricting the search predictions to
// addresses in the US and Canada.
autocomplete = new google.maps.places.Autocomplete(address1Field, {
componentRestrictions: { country: ["us", "ca"] },
fields: ["address_components", "geometry"],
types: ["address"],
});
// When the user selects an address from the drop-down, populate the address fields in the form.
autocomplete.addListener("place_changed", fillInAddress);
}
function fillInAddress() {
// Get the place details from the autocomplete object.
const place = autocomplete.getPlace();
let address1 = "";
let postcode = "";
// Get each component of the address from the place details,
// and then fill-in the corresponding field on the form.
// place.address_components are google.maps.GeocoderAddressComponent objects
// which are documented at http://goo.gle/3l5i5Mr
for (const component of place.address_components) {
// @TS-ignore remove once typings fixed
const componentType = component.types[0];
switch (componentType) {
case "street_number": {
address1 = `${component.long_name} ${address1}`;
break;
}
case "route": {
address1 += component.short_name;
break;
}
case "postal_code": {
postcode = `${component.long_name}${postcode}`;
break;
}
case "postal_code_suffix": {
postcode = `${postcode}-${component.long_name}`;
break;
}
case "locality":
//change .hs-city below to the class for your city input field if necessary
//comment out or delete the line below if no city field in your form
document.querySelector(".hs-city .hs-input").value = component.long_name;
break;
case "administrative_area_level_1": {
//change .hs-state below to the class for your city input field if necessary
//comment out or delete the line below if no state field in your form
document.querySelector(".hs-state .hs-input").value = component.short_name;
break;
}
case "country":
//change .hs-country below to the class for your city input field if necessary
//comment out or delete the line below if no country field in your form
document.querySelector(".hs-country .hs-input").value = component.long_name;
break;
}
}
address1Field.value = address1;
//comment out or delete the line below if no postal code field in form
postalField.value = postcode;
}
window.initAutocomplete = initAutocomplete;
Oct 18, 2023 6:14 PM
Hi! Wondering if you have a live form running off of this? I've got a version running using places now but there are a few quirks I'd like to iron out; would love to see if yours runs more smoothly?
Oct 15, 2021 8:21 AM
Hi, you can add Loqate's address autocomplete on HubSpot forms, and here's a handy guide to show you how to: https://www.loqate.com/resources/support/setup-guides/hubspot/
May 12, 2021 11:35 PM
Wondering if anyone ever got SmartyStreets intergrated with HubSpot.
Feb 25, 2025 11:20 AM
I did, and it works great. They have a handy guide to show you how: https://www.smarty.com/articles/hubspot-address-autocomplete-guide
Sep 15, 2020 10:50 AM
Hi @chrispower and @joneichler and @SabeFlex ,
Please upvote this idea here so HubSpot will do this for us 🙂
Dec 4, 2018 4:48 PM
Unfortunately I'm not familiar with smarty streets, but if your not married to that option, you could use the google maps autocomplete api.
This is a pretty good tutorial on it : https://www.w3docs.com/learn-javascript/places-autocomplete.html
You would just need to set the script to target the ID of the hubspot form field.
Dec 5, 2018 11:27 AM
Hi
I am actually already familiar with smartystreets and have implemented it elsewhere in standalone websites. The part I'm not clear on is how I would integrate it with a hubspot form.
"You would just need to set the script to target the ID of the hubspot form field."
Can you elaborate on that please?
Thanks
Chris
Dec 5, 2018 11:31 AM
With the google solution you just target a specific Form field (using standard css targeting) to be your main address field and then you also set which fields it will poplulate once the user selects the address.
Sorry I can't be of more assistance.
Oct 28, 2019 4:52 PM - edited Oct 28, 2019 4:53 PM
This sounds like a great solution I think a lot of people would utilize. Would you mind sharing a bit more about how you accomplished this?
The link you shared doesn;t contain any info about filling the remaining address fields with the info retrieved from the Places API. Would you mind sharing your implementation? Even a brief example of some working code would go a long way.
Feb 13, 2020 4:41 PM
Did you ever receive any additional instructions?
Feb 25, 2025 11:22 AM
Smarty (formerly SmartyStreets) recently released a tutorial on how to get autocomplete working in Hubspot, and the principles outlined in their tutorial answer the question above.
https://www.smarty.com/articles/hubspot-address-autocomplete-guide