azishapidin/indoregion
Laravel package for Indonesian administrative region data, offering provinces, regencies, districts, and villages with migrations, Eloquent models, relationships, and seeders. Useful for address forms, shipping, localization, and dependent dropdowns.
Install and publish:
composer require azishapidin/indoregion
php artisan vendor:publish --provider="AzisHapidin\IndoRegion\IndoRegionServiceProvider"
php artisan migrate
Then seed the region tables. The dataset is large (roughly 34–38 provinces, ~514 regencies/cities, ~7,200 districts, ~83,000 villages), so run it on the CLI with a generous memory limit:
php artisan db:seed --class="AzisHapidin\IndoRegion\Seeders\IndoRegionSeeder"
php artisan tinker --execute="dump(AzisHapidin\IndoRegion\Models\Province::count());"
Look at these first:
config/indoregion.php — table names and any publishable settings.src/Models/{Province, Regency, District, Village}.php — relationships run downward (regencies(), districts(), villages()) and backward (province(), regency(), district()).provinces(id, name), regencies(id, province_id, name), districts(id, regency_id, name), villages(id, district_id, name). IDs are BPS codes, not sequential counters.First real use case: a cascading address picker on a checkout or profile form. Render Province::orderBy('name')->get(), then hit your own endpoint with ?province_id= to fetch regencies, etc., and persist the deepest selected ID (village or district) alongside the resolved codes on the address record.
Cascading dropdown endpoint. A single controller action keyed by level keeps the front end simple:
public function children(Request $request)
{
$level = $request->enum('level', RegionLevel::class);
$parent = $request->integer('parent_id');
$items = match ($level) {
RegionLevel::Regency => Regency::where('province_id', $parent)->orderBy('name')->get(['id', 'name']),
RegionLevel::District => District::where('regency_id', $parent)->orderBy('name')->get(['id', 'name']),
RegionLevel::Village => Village::where('district_id', $parent)->orderBy('name')->get(['id', 'name']),
};
return response()->json($items);
}
Cache the static tree. Region data barely changes; Cache::rememberForever("regions.regencies.{$provinceId}", ...) is cheap and effective. Bust it in your deploy step after a reseed.
Store both an ID and the code. Your addresses table should keep province_id, regency_id, district_id, village_id for FK integrity, plus the human-readable names snapshot if addresses must remain stable across package updates.
Validate hierarchy, not just existence. exists:villages,id is not enough — assert that the village belongs to the submitted district, which belongs to the regency, which belongs to the province. A single chained query or a whereHas rule handles it.
Eager-load for display. A user list showing full addresses will N+1 badly: User::with('village.district.regency.province'). The four-level chain is a classic performance trap.
Testing. Seed only the subtree you need in fixtures (e.g., Province::insert([...]) for one regency) rather than running the full seeder in test setup — it will dominate your CI time.
memory_limit=-1, disable FK checks around truncate-and-reseed, and wrap in a transaction where your driver supports it. On shared hosting, run it via CLI, never a web request.11 = Aceh, regency 11.01, etc.). Never assume they start at 1 or are contiguous. Treat them as opaque identifiers when joining externally (e.g., to government datasets or logistics APIs).Kabupaten ... vs Kota ...), not by a column — the table does not separate them.composer.json and plan a controlled reseed. Addresses referencing removed IDs will break unless you keep the name snapshot or resolve by code.addresses.village_id FK will block a truncate. Use restrictOnDelete or nullOnDelete deliberately, and script the reseed order (children first, or temporarily disable constraints).Kabupaten /Kota /Kecamatan /Kelurahan /Desa before doing LIKE matching, or users typing "Bandung" will get inconsistent results between the city and the regency.How can I help you explore Laravel packages today?