davidamunga / kenya-locations
Kenyan administrative divisions — counties, sub-counties, constituencies, wards, localities, and areas.
Requires
- php: ^8.2
- ext-json: *
- ext-mbstring: *
Requires (Dev)
- phpunit/phpunit: ^11.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Kenyan administrative divisions — counties, sub-counties, constituencies, wards, localities, and areas — as a typed PHP 8.2+ Composer library.
47 counties · 302 sub-counties · 290 constituencies · 1,450 wards · 917 localities · 1,829 areas
JSON is loaded lazily from the shared data/ files and cached for the
process lifetime. No framework or service container is required.
This is the PHP counterpart to the JavaScript, React, Kotlin, Swift, and Dart libraries in the same kenya-locations project, all sourced from the same underlying dataset.
Development happens in the monorepo (packages/php). Packagist is served
from the split repo
kenya-locations-php.
Installation
composer require davidamunga/kenya-locations
Works in any PHP 8.2+ project — including WordPress themes and plugins.
There is no published WordPress plugin. In a Bedrock site, mu-plugin, or project-root Composer install:
use KenyaLocations\KenyaLocations; $nairobi = KenyaLocations::getCountyByName('Nairobi'); $wards = KenyaLocations::getWardsInConstituency('Westlands');
A drop-in plugin (REST drill-down, County → Locality → Area fields,
WooCommerce store address and checkout) lives in the monorepo at
examples/wordpress.
GitHub v* releases attach a zip with this library already vendored.
Usage
use KenyaLocations\KenyaLocations; use KenyaLocations\SearchType; $counties = KenyaLocations::getCounties(); $nairobi = KenyaLocations::getCountyByName('Nairobi'); echo $nairobi?->capital; // Nairobi echo $nairobi?->population2019; // 4397073 echo $nairobi?->region; // Nairobi
Drilling down
$constituencies = KenyaLocations::getConstituenciesInCounty('Nairobi'); $wards = KenyaLocations::getWardsInConstituency('Westlands'); $localities = KenyaLocations::getLocalitiesInCounty('Nairobi'); $areas = KenyaLocations::getAreasInLocality('Karen'); // Bura exists in three counties, so pass the county $bura = KenyaLocations::getAreasInLocality('Bura', 'Garissa');
Resolving upward
$constituency = KenyaLocations::getConstituencyOfWard('Mountain view'); echo $constituency?->name; // Westlands // Prefer a ward code when the name is reused (e.g. Township) $byCode = KenyaLocations::getConstituencyOfWard('1370'); KenyaLocations::getCountyOfWard('1370'); KenyaLocations::getCountyOfConstituency('Westlands'); KenyaLocations::getLocalityOfArea('Gigiri'); // Same locality name in more than one county $matches = KenyaLocations::getLocalitiesByName('Westlands'); $nairobiOnly = KenyaLocations::getLocality('Westlands', 'Nairobi');
Search
Search is case-insensitive and typo-tolerant, matching common misspellings via Levenshtein distance. It covers counties, sub-counties, constituencies, wards, localities, and areas.
$results = KenyaLocations::search('Nairob', limit: 20); $wardsOnly = KenyaLocations::searchByType('West', SearchType::Ward); foreach ($results as $result) { echo $result->type->value . ': ' . $result->name() . PHP_EOL; }
Data model
County (47)
├── code, name, capital, areaKm2, population2019, region, postalCode, isoCode, aliases
├── Locality → Area (informal addressing: estates, neighbourhoods)
│ 917 localities · 1,829 areas
└── Constituency → Ward (electoral / administrative)
290 constituencies · 1,450 wards
└── SubCounty (302)
| Type | Fields |
|---|---|
County |
code, name, capital, areaKm2, population2019, region, postalCode, isoCode, aliases |
SubCounty |
code, name, county, countyCode |
Constituency |
code, name, county, countyCode |
Ward |
code, name, constituency, constituencyCode, countyCode |
Locality |
name, county, countyCode |
Area |
name, locality, county, countyCode |
isoCode is the ISO 3166-2:KE code (KE-30 for Nairobi). aliases are other
names that resolve to the county, so getCountyByName('Nairobi City') and any
county argument accept them. Relationships are joined on countyCode and
constituencyCode, not on names.
API reference
| Method | Returns |
|---|---|
getCounties() |
list<County> |
getCountyByCode(code) |
?County |
getCountyByName(name) |
?County |
getSubCounties() |
list<SubCounty> |
getSubCountyByCode(code) |
?SubCounty |
getSubCountyByName(name) |
?SubCounty |
getSubCountiesInCounty(nameOrCode) |
list<SubCounty> |
getCountyOfSubCounty(nameOrCode) |
?County |
getConstituencies() |
list<Constituency> |
getConstituencyByCode(code) |
?Constituency |
getConstituencyByName(name) |
?Constituency |
getConstituenciesInCounty(nameOrCode) |
list<Constituency> |
getCountyOfConstituency(nameOrCode) |
?County |
getConstituencyOfWard(wardNameOrCode) |
?Constituency |
getWards() |
list<Ward> |
getWardByCode(code) |
?Ward |
getWardByName(name) |
?Ward |
getWardsByName(name) |
list<Ward> |
getWardsInConstituency(nameOrCode) |
list<Ward> |
getWardsInCounty(nameOrCode) |
list<Ward> |
getWardsInSubCounty(nameOrCode) |
list<Ward> |
getCountyOfWard(wardNameOrCode) |
?County |
getSubCountyOfWard(wardNameOrCode) |
?SubCounty |
getLocalities() |
list<Locality> |
getLocalityByName(name) |
?Locality |
getLocality(name, countyName?) |
?Locality |
getLocalitiesByName(name) |
list<Locality> |
getLocalitiesInCounty(nameOrCode) |
list<Locality> |
getCountyOfLocality(name) |
?County |
getAreas() |
list<Area> |
getAreaByName(name) |
?Area |
getAreasByName(name) |
list<Area> |
getAreasInLocality(localityName, county?) |
list<Area> |
getAreasInCounty(nameOrCode) |
list<Area> |
getCountyOfArea(name) |
?County |
getLocalityOfArea(name) |
?Locality |
search(query, limit: 20, types: null) |
list<SearchResult> |
searchByType(query, type, limit: 20) |
list<SearchResult> |
Name lookups are case-insensitive. When nothing matches exactly, a looser
match that ignores spaces, hyphens, apostrophes and accents is tried
(Muranga finds Murang'a). Codes are zero-padded, so '47' finds county
047. County, constituency, and sub-county arguments accept a name or an
administrative code.
Singular by-name lookups return null when a name is reused (for example the
Township ward or the Makadara locality) instead of an arbitrary record.
Use the plural ...ByName methods, a ward code, or pass a county to
getLocality() / getAreasInLocality() in that case.
Local development
From this directory:
composer install
composer test
The package ships a copy of the monorepo data/*.json files in data/ so a
Packagist install is self-contained. After editing the shared JSON at the
repo root, refresh that copy:
composer copy-data
CI fails if the copy is stale. Runtime also falls back to the monorepo
data/ directory, so local tests work even before you copy. No codegen
step is required.
Contributing
Data corrections and additions belong in the shared data/ JSON files at the
root of the
kenya-locations monorepo.
See the main repo's
CONTRIBUTING
guide.
License
MIT © David Amunga
Data sourced from the Independent Electoral and Boundaries Commission (IEBC) and Kenya National Bureau of Statistics (KNBS).