Ecommerce Checkout & API
Connect Division → District → Upazila → Union fields to an ecommerce checkout, validate the address on your server, and add Bangladesh postcode lookup.
Recommended checkout flow
Division
↓ division_id
District
↓ district_id
Upazila
↓ upazila_id
Union
↓ postcode lookup
Address and delivery zone
Use parent IDs for every relationship. Do not connect records by comparing English or Bengali names. Store the selected IDs and save the selected names as an order snapshot.
Live checkout auto-fill demo
Use the controls below to test the cascade. Each child list is filtered by the selected parent ID. The postcode field checks the bundled Bangladesh postcode data.
Loading checkout data…
Suggested API routes
GET /api/locations/divisions
GET /api/locations/districts?division_id=6
GET /api/locations/upazilas?district_id=1
GET /api/locations/unions?upazila_id=1
GET /api/locations/postcodes/1206
POST /api/checkout/validate-address
Browser linking example
divisionSelect.addEventListener('change', () => {
const id = divisionSelect.value;
fillSelect(
districtSelect,
districts.filter(item => String(item.division_id) === id)
);
});
districtSelect.addEventListener('change', () => {
const id = districtSelect.value;
fillSelect(
upazilaSelect,
upazilas.filter(item => String(item.district_id) === id)
);
});
upazilaSelect.addEventListener('change', () => {
const id = upazilaSelect.value;
fillSelect(
unionSelect,
unions.filter(item => String(item.upazilla_id ?? item.upazila_id) === id)
);
});
Complete auto-fill checkout snippet
Copy this example into a page served from the repository root, or change the five JSON paths to the location where your storefront serves the data files. The helper removes the phpMyAdmin metadata envelope before populating the selectors.
<form id="checkout-address">
<select id="division" name="division_id" required></select>
<select id="district" name="district_id" required disabled></select>
<select id="upazila" name="upazila_id" required disabled></select>
<input id="postcode" name="postcode" inputmode="numeric"
autocomplete="postal-code" placeholder="Postcode" required>
</form>
const sources = {
divisions: 'divisions/divisions.json',
districts: 'districts/districts.json',
upazilas: 'upazilas/upazilas.json',
postcodes: 'postcode-bd/postcode.json',
};
const records = value => {
const table = value.find(item => Array.isArray(item.data));
return table ? table.data : value;
};
const response = await Promise.all(
Object.values(sources).map(path => fetch(path).then(r => r.json()))
);
const [divisions, districts, upazilas, postcodes] = response;
function setOptions(select, rows, placeholder) {
select.replaceChildren(new Option(placeholder, ''));
for (const row of rows) select.add(new Option(row.name, row.id));
select.disabled = rows.length === 0;
}
const division = document.querySelector('#division');
const district = document.querySelector('#district');
const upazila = document.querySelector('#upazila');
const postcode = document.querySelector('#postcode');
setOptions(division, records(divisions), 'Select division');
division.addEventListener('change', () => {
setOptions(district, records(districts).filter(row =>
String(row.division_id) === division.value
), 'Select district');
setOptions(upazila, [], 'Select upazila');
});
district.addEventListener('change', () => {
setOptions(upazila, records(upazilas).filter(row =>
String(row.district_id) === district.value
), 'Select upazila');
});
postcode.addEventListener('change', () => {
const found = Object.keys(postcodes).some(key =>
key.trim() === postcode.value.trim()
);
postcode.setCustomValidity(found ? '' : 'Postcode not found');
});
The browser snippet provides dynamic population and basic postcode feedback. The tested reusable implementation is available as assets/checkout-autofill.mjs. Beginners can copy the complete frontend and PyPI backend example. Before placing an order, repeat the parent-child and postcode-to-district checks on your backend as described in the full API guide ↗.
Run the automated frontend tests locally with node --test tests/frontend/checkout-autofill.test.mjs. The tests run automatically in GitHub Actions for changes to the checkout module or its tests.
Python API example
from bangladesh_geo_data import (
get_districts,
get_postcodes,
get_upazilas,
get_unions,
)
districts = get_districts(division_id=6)
upazilas = get_upazilas(district_id=1)
unions = get_unions(upazila_id=1)
postcode = get_postcodes(1206)
Server-side validation
Browser validation is only a user-interface feature. Before creating an order, the backend must verify that the district belongs to the selected division, the upazila belongs to the district, and the union belongs to the upazila. It should also normalize the postcode, check delivery coverage, calculate shipping on the server, and store the final address snapshot.
Postcode and delivery zones
A postcode identifies a postal area; it does not prove that a street address is valid or that a courier serves it. Use get_postcodes() or your own imported postcode table for lookup, then apply your store’s delivery-zone, shipping-fee, and estimated-delivery rules.
Continue reading
See the Getting Started page for data formats, the Data Schema page for field definitions, the Python Package page for installation, and the full Ecommerce API Guide ↗ for FastAPI, Flask, Django, PHP, SQL, caching, security, and production examples.