Learn how to build a complete address form with country → state → city dropdowns using CountryDataAPI's select endpoints.
This guide shows you how to create cascading select inputs for:
The select endpoints are optimized for forms, returning only id and name fields for minimal payload size and maximum performance.
First, load all countries when your page loads. Countries rarely change, so you can cache this data in localStorage for better performance.
const API_KEY = 'your-api-key';
const BASE_URL = 'https://api.countrydataapi.com/v1';
async function loadCountries() {
try {
const response = await fetch(
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=en`
);
const { success, data, error } = await response.json();
if (!success) {
console.error('API Error:', error);
return;
}
const countrySelect = document.getElementById('country');
data.forEach(country => {
const option = new Option(country.name, country.id);
countrySelect.add(option);
});
} catch (err) {
console.error('Network Error:', err);
}
}
// Call on page load
document.addEventListener('DOMContentLoaded', loadCountries);
Tip: Cache the countries list in localStorage to avoid repeated API calls. Countries don't change frequently!
When a user selects a country, load the corresponding states/provinces and reset the city dropdown.
document.getElementById('country').addEventListener('change', async (e) => {
const countryId = e.target.value;
const stateSelect = document.getElementById('state');
const citySelect = document.getElementById('city');
// Reset state and city dropdowns
stateSelect.innerHTML = '<option value="">Select state...</option>';
citySelect.innerHTML = '<option value="">Select city...</option>';
stateSelect.disabled = true;
citySelect.disabled = true;
if (!countryId) return;
try {
const response = await fetch(
`${BASE_URL}/select/states?apikey=${API_KEY}&country=${countryId}&lang=en`
);
const { success, data, error } = await response.json();
if (!success) {
console.error('API Error:', error);
return;
}
data.forEach(state => {
stateSelect.add(new Option(state.name, state.id));
});
stateSelect.disabled = false;
} catch (err) {
console.error('Network Error:', err);
}
});
Finally, load cities when a state is selected.
document.getElementById('state').addEventListener('change', async (e) => {
const stateId = e.target.value;
const citySelect = document.getElementById('city');
// Reset city dropdown
citySelect.innerHTML = '<option value="">Select city...</option>';
citySelect.disabled = true;
if (!stateId) return;
try {
const response = await fetch(
`${BASE_URL}/select/cities?apikey=${API_KEY}&state=${stateId}&lang=en`
);
const { success, data, error } = await response.json();
if (!success) {
console.error('API Error:', error);
return;
}
data.forEach(city => {
citySelect.add(new Option(city.name, city.id));
});
citySelect.disabled = false;
} catch (err) {
console.error('Network Error:', err);
}
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Address Form - CountryDataAPI</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #333;
}
select {
width: 100%;
padding: 10px;
border: 2px solid #e2e8f0;
border-radius: 6px;
font-size: 16px;
background-color: white;
cursor: pointer;
}
select:disabled {
background-color: #f7fafc;
cursor: not-allowed;
opacity: 0.6;
}
select:focus {
outline: none;
border-color: #3b82f6;
}
button {
background-color: #3b82f6;
color: white;
padding: 12px 24px;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
width: 100%;
}
button:disabled {
background-color: #cbd5e0;
cursor: not-allowed;
}
</style>
</head>
<body>
<h1>Address Form</h1>
<form id="address-form">
<div class="form-group">
<label for="country">Country *</label>
<select id="country" name="country" required>
<option value="">Select country...</option>
</select>
</div>
<div class="form-group">
<label for="state">State/Province *</label>
<select id="state" name="state" required disabled>
<option value="">Select state...</option>
</select>
</div>
<div class="form-group">
<label for="city">City *</label>
<select id="city" name="city" required disabled>
<option value="">Select city...</option>
</select>
</div>
<div class="form-group">
<label for="street">Street Address *</label>
<input type="text" id="street" name="street" required
style="width: 100%; padding: 10px; border: 2px solid #e2e8f0; border-radius: 6px;">
</div>
<button type="submit">Submit Address</button>
</form>
<script src="address-form.js"></script>
</body>
</html>
Handle the form submission to get the selected values:
document.getElementById('address-form').addEventListener('submit', (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const address = {
country: formData.get('country'),
state: formData.get('state'),
city: formData.get('city'),
street: formData.get('street')
};
console.log('Address submitted:', address);
// Send to your backend or process as needed
// fetch('/api/save-address', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify(address)
// });
});
This implementation is highly efficient with token usage:
Example: A user filling out the form completely uses only 3 tokens total.
Countries don't change often. Cache them in localStorage:
async function loadCountries() {
const cached = localStorage.getItem('countries');
if (cached) {
const data = JSON.parse(cached);
populateCountrySelect(data);
return;
}
const response = await fetch(
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=en`
);
const { success, data } = await response.json();
if (success) {
localStorage.setItem('countries', JSON.stringify(data));
populateCountrySelect(data);
}
}
function populateCountrySelect(countries) {
const select = document.getElementById('country');
countries.forEach(country => {
select.add(new Option(country.name, country.id));
});
}
Show visual feedback while loading data:
async function loadStates(countryId) {
const stateSelect = document.getElementById('state');
stateSelect.innerHTML = '<option>Loading...</option>';
stateSelect.disabled = true;
// ... fetch states
stateSelect.disabled = false;
}
If you add a searchable dropdown, debounce the input:
let debounceTimer;
searchInput.addEventListener('input', (e) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
filterOptions(e.target.value);
}, 300);
});
Always provide user-friendly error messages:
try {
// API call
} catch (err) {
const select = document.getElementById('state');
select.innerHTML = '<option>Error loading states. Please try again.</option>';
}
The API supports multiple languages. Change the lang parameter:
// Spanish
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=es`
// Portuguese
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=pt`
// French
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=fr`
// German
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=de`
// Italian
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=it`
If you have questions or need assistance:
Pro Tip: For production applications, consider implementing a custom dropdown component with search functionality to handle countries with many states/cities. Libraries like Select2, Choices.js, or custom autocomplete can significantly improve UX.