In the complex world of HubSpot CMS development, there are everyday tasks… and then there are moments that demand inventive problem-solving, cross-language fluency, and real-time validation logic across client-side UI and external APIs. This article dives into one such real-world use case where I transformed a static HubSpot form into a dynamic, validated, responsive user experience.
I was tasked with building an embedded HubSpot form that adapts based on user country — specifically US and Canada — displaying respective state and postal code fields, validating them via external APIs, and handling UI changes on the fly. The default HubSpot form functionality wasn’t enough for this level of interaction.
Some of the core requirements were:
Conditionally display state
and postal code
fields only for users from the US or CA.
Validate postal codes using external APIs like Zippopotam.us.
Auto-suggest states as users typed.
Ensure compatibility within HubSpot’s CMS modules without breaking server-side rendering or HubL scope.
Instead of relying on HubSpot’s default form field handling, I wrapped the embedded form within a custom module, injecting JavaScript that:
Hooked into onCountryChange()
logic.
Dynamically mounted or unmounted fields via the DOM.
Pulled validation responses via fetch()
with debounce.
Styled field visibility and errors using HubSpot’s recommended hs-error-msg
patterns.
Country Detection
const countryField = document.querySelector('[name="country"]');
countryField.addEventListener('change', handleCountryChange);
function handleCountryChange() {
const val = countryField.value;
toggleUSCAFields(val);
}
Field Toggling
function toggleUSCAFields(country) {
const stateField = document.getElementById('state-field');
const postalField = document.getElementById('postal-field');
const shouldShow = ['United States', 'Canada'].includes(country);
stateField.style.display = shouldShow ? 'block' : 'none';
postalField.style.display = shouldShow ? 'block' : 'none';
}
Postal Code API Validation (Zippopotam)
const postalInput = document.querySelector('#postal-code');
postalInput.addEventListener('input', debounce(validatePostal, 500));
function validatePostal() {
const postal = postalInput.value;
const country = countryField.value === 'Canada' ? 'CA' : 'US';
fetch(`https://api.zippopotam.us/${country}/${postal}`)
.then(res => {
if (!res.ok) throw new Error('Invalid postal');
return res.json();
})
.then(data => showSuccess())
.catch(() => showError());
}
Auto Suggestion for State Input (for free-typed fields)
I used a local JSON of US/CA states mapped by code and label, and added dynamic suggestions with keyboard navigation — completely inside the form.
I accounted for:
Mobile vs desktop behavior.
Browser autofill conflicts.
HubSpot's internal field validation layer (hs-validation-error
).
Default values from pre-filled form states.
This implementation not only enhanced the UX but also prevented invalid submissions and helped sync accurate user location data directly into HubSpot CRM. It performed flawlessly within a modular CMS layout and even supported multilingual variants without major refactor.
Don’t limit yourself to HubL and form builder UI. When needed, step beyond and inject logic the smart way.
Use external validation only if latency can be mitigated — debounce and caching are key.
Always test for field rehydration if the form is reused in a multi-step process or gets re-rendered post submission.
HubL
Vanilla JS (no jQuery)
JSON APIs (Zippopotam)
Inline module-level styling
HubSpot embedded forms
If you’re developing advanced HubSpot CMS modules and need interactive, intelligent forms — this approach can unlock flexibility that blends natively with HubSpot's ecosystem.
Want to see a live demo or use this logic in your own project? Reach out or fork the logic into your own custom module today.