API Documentation - Endpoints & Examples

Address Form with Cascading Selects

Build professional address forms with cascading country, state, and city selects

Learn how to build a complete address form with country → state → city dropdowns using CountryDataAPI's select endpoints.

Overview

This guide shows you how to create cascading select inputs for:

  • Country selection - Load all available countries
  • State/Province selection - Filtered by selected country
  • City selection - Filtered by selected state

The select endpoints are optimized for forms, returning only id and name fields for minimal payload size and maximum performance.

Prerequisites

  • A CountryDataAPI key (Get one here)
  • Basic knowledge of JavaScript and HTML
  • Modern web browser with fetch API support

Step 1: Load Countries

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!

Step 2: Load States When Country Changes

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);
  }
});

Step 3: Load Cities When State Changes

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);
  }
});

Complete HTML Example

<!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>

Form Submission

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)
  // });
});

Token Usage

This implementation is highly efficient with token usage:

  • 1 token for loading countries (once per page load, can be cached)
  • 1 token per state lookup (when user selects a country)
  • 1 token per city lookup (when user selects a state)

Example: A user filling out the form completely uses only 3 tokens total.

Optimization Tips

1. Cache Country Data

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));
  });
}

2. Add Loading States

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;
}

3. Debounce Search Input

If you add a searchable dropdown, debounce the input:

let debounceTimer;
searchInput.addEventListener('input', (e) => {
  clearTimeout(debounceTimer);
  debounceTimer = setTimeout(() => {
    filterOptions(e.target.value);
  }, 300);
});

4. Handle Errors Gracefully

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>';
}

Multi-language Support

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`

Next Steps

Need Help?

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.