Developing a Geolocation Module for 1C-Bitrix
Geolocation is not just "determine the city". On an e-commerce site, geolocation affects delivery method availability, product assortment (not all products are shipped to a region), warehouse source for shipment, regional phone number in the header, regional promotions. Bitrix has bitrix:geo for basic city detection, but it is not integrated with the catalog, basket, and custom components — each connection must be built independently.
Architecture
Module vendor.geo solves three tasks:
- Region detection — by IP, by browser geolocation, by explicit user choice
- Region storage — hierarchy: country → region → city, with all metadata
- Application — API for components: current region, delivery filtering, regional content
ORM tables:
-
b_vendor_geo_country— countries: id, name, iso2, iso3, phone_code -
b_vendor_geo_region— regions/oblasts: id, country_id, name, timezone -
b_vendor_geo_city— cities: id, region_id, name, latitude, longitude, population -
b_vendor_geo_user_location— user's selected region: user_id or session_id, city_id, detection_method (ip/browser/manual), created_at
City Detection by IP
Three options in decreasing order of accuracy and cost:
MaxMind GeoIP2 (recommended for high accuracy):
$reader = new \GeoIp2\Database\Reader('/path/to/GeoLite2-City.mmdb');
$record = $reader->city($_SERVER['REMOTE_ADDR']);
$cityName = $record->city->names['ru'] ?? $record->city->name;
$regionName = $record->mostSpecificSubdivision->names['ru'] ?? null;
ip-api.com (free, 45 req/min):
$data = json_decode(file_get_contents("http://ip-api.com/json/{$ip}?lang=ru&fields=city,regionName"), true);
dadata.ru (more accurate for Russia):
// POST https://suggestions.dadata.ru/suggestions/api/4_1/rs/iplocate/address
The result is cached by IP address for 24 hours. For corporate networks (one IP = entire office) and CDN proxies, detection is less accurate — in these cases, the result is marked as low_confidence.
City Confirmation Popup
On first visit, a popup is shown: "Is your city Moscow?". If the user clicks "No, choose another" — a modal window with city search opens:
GET /bitrix/components/vendor/geo.city-search/ajax.php?q=Krasnod
→ [{"id":12,"name":"Krasnodar","region":"Krasnodar Krai"}, ...]
Search by b_vendor_geo_city.name with LIKE 'Krasnod%', pre-prepared index on the column. Selected city is saved in cookie and b_vendor_geo_user_location.
Geolocation Application
Regional phone in header:
$city = \Vendor\Geo\GeoService::getCurrentCity();
$phone = RegionalPhoneTable::getByCity($city['ID']) ?? Option::get('vendor.geo', 'default_phone');
Delivery method filtering:
In the checkout component, before displaying deliveries, filter by region:
$deliveries = \Bitrix\Sale\Delivery\Services\Manager::getActiveList();
$cityId = GeoService::getCurrentCityId();
$deliveries = array_filter($deliveries, function($delivery) use ($cityId) {
$regions = DeliveryRegionTable::getAvailableRegions($delivery->getId());
return empty($regions) || in_array($cityId, $regions);
});
Nearest warehouse for availability calculation:
If the store works with multiple warehouses, the nearest is determined by coordinates → haversine distance.
Regional Content in Infoblock
Infoblock elements and sections can be linked to visibility regions through the RegionBinding HL-block. The vendor:geo.iblock.filter component automatically adds a filter by current region to the query.
Development Timeline
| Stage | Duration |
|---|---|
| ORM-tables, city database import | 1 day |
| City detection by IP (MaxMind + fallback) | 1 day |
| City confirmation popup, city search | 1 day |
| Current region API, session and cookie | 0.5 day |
| Regional phones, content | 1 day |
| Delivery filtering by region | 1 day |
| Nearest warehouse by coordinates | 1 day |
| Testing | 0.5 day |
Total: 7 working days. Geolocation via browser Geolocation API (GPS/WiFi) — +1 day.







