Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Indonesia Laravel Package

laravolt/indonesia

Laravel package for Indonesian regional data: provinces, cities/regencies, districts, and villages, with models, migrations, seeders, and helpers for addresses, postal codes, and location lookups. Quickly add accurate Indonesia area data to your app.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require laravolt/indonesia
php artisan vendor:publish --provider="Laravolt\Indonesia\ServiceProvider"   # config + migrations
php artisan migrate
php artisan indonesia:seed                                                   # ~83k villages, takes minutes

Confirm the exact artisan namespace on your version with php artisan list indonesia — the seed command has been renamed across releases, and publishing is tag-driven (--tag=config, --tag=migrations) if you want to skip one of them.

Where to look first:

  1. config/indonesia.php — the models map. This is the real API surface; everything else (facade, relations, routes) resolves through it.
  2. Laravolt\Indonesia\Models\{Province,City,District,Village} — adjacency-list models with province_id / city_id / district_id, plus name and code (BPS code) columns.
  3. Laravolt\Indonesia\Indonesia — a query helper (Indonesia::findProvince($id, ['cities']), allProvinces(), etc.) that returns the configured model instances with eager loads.

The canonical first use case is a registration/address form: a province <select> populated from Province::orderBy('name')->pluck('name', 'id'), then dependent city → district → village selects. Persist village_id on your address table (it's the finest-grained key and carries postal_code), and derive the rest through the village's parent relations rather than duplicating four columns — unless you need the values to survive a re-seed (see Gotchas).

Implementation Patterns

Cascading selects. Serve one endpoint per level and hit it from Alpine/Livewire/your JS of choice. Filter by parent foreign key, never by name:

Route::get('/cities/{province}', fn (Province $province) =>
    $province->cities()->orderBy('name')->get(['id', 'name'])
);

For nested payloads, prefer the package helper over hand-rolled eager loads: Indonesia::findProvince($id, ['cities.districts']).

Model swapping. Don't edit the published models. Subclass them and repoint the config map:

// config/indonesia.php
'models' => ['province' => App\Models\Province::class, /* ... */],

Then add your own relations (addresses(), scopes, accessors like full_name). Anything in the package that resolves models — facade, relations, routes — picks this up. Run php artisan config:cache afterwards, or you'll silently get package models in production while local works.

Validation. Use real integrity checks rather than trusting the client's cascade: Rule::exists('villages', 'id') combined with a Rule::exists('districts', 'id')->where('city_id', $request->city_id) style guard, or a small custom rule that walks the parent chain.

Testing. Never run the full seed in the test suite. Seed a slice in a seeder (Province::insert(...), one city) or import a fixture of ~20 rows; the schema is published so RefreshDatabase gives you the tables for free. If you need realistic data, build factories that call insertOrIgnore with explicit IDs so parent links stay deterministic.

Search / autocomplete. Village names are ambiguous; match on name and show district.name, city.name in the label. Add an index on villages.name (and consider a LIKE 'prefix%' instead of '%term%' — the table is large enough that leading wildcards hurt).

Caching dropdowns. Provinces and cities are effectively static between releases; Cache::rememberForever('id.provinces', fn () => Province::pluck('name','id')) with an explicit invalidation step in your upgrade/seed deploy job removes 95% of the query traffic from address forms.

Data refresh workflow. Treat region data as a deployable artifact: bump the package, publish any new migrations, run the seed command, then run your own reconciliation (e.g. re-point addresses whose village_id disappeared). Wrap the whole thing in a queued job or a deploy hook, not a request.

Gotchas and Tips

  • Seeding performance is the #1 pain point. 80k+ village inserts through Eloquent will crawl. Use DB::disableQueryLog(), wrap in a transaction (or chunked transactions), drop indexes before the load and rebuild after, and prefer the packaged seed command's chunking over looping models yourself. On a constrained MySQL instance, importing a plain SQL dump is often 10× faster than the artisan command.
  • Truncation order matters if foreign keys are enforced: villages → districts → cities → provinces. TRUNCATE with FKs on will fail; use DELETE or toggle FOREIGN_KEY_CHECKS.
  • BPS codes are not immutable. Papua/South Papua re-codes changed province and city codes. If you key your own data by code instead of id, every upgrade is a migration. Store village_id locally and treat code as reference data.
  • Storing denormalized names is a trap in the other direction. If you snapshot province_name on an order for historical accuracy, that's fine — but don't also try to keep it in sync with a re-seed. Decide per table.
  • Published migrations can duplicate. Re-running vendor:publish after an upgrade may emit a second migration with a new timestamp; check migrations before running, and add your extra columns (postal_code overrides, custom flags, indexes on province_id) in your own migration rather than editing the package's.
  • config:cache and model swaps. Any config change to models requires a cache clear. A stale cache here presents as "my accessor doesn't exist" errors that look like a bug in the package.
  • Auto-discovery + aliases. On modern Laravel the service provider and Indonesia facade are auto-discovered; on older skeletons you must register both manually. A Class "Indonesia" not found almost always means a missing alias entry, not a broken install.
  • Routes may collide. If routing is enabled in config, the package claims an /indonesia-ish prefix. Disable it and write your own thin controllers if you already own that namespace or need auth/policies on the endpoints.
  • v0.41 (Laravel 13 compatibility) is a constraint-widening release — no schema, API, or behaviour changes. Upgrade is low risk; the only thing to re-verify is your swapped models and any signature you've overridden (relations, scopes) still matching, plus a config cache clear on deploy. Re-run the seed only if you also want refreshed region data; the package version bump alone doesn't require it.
  • Extension point to remember: when you need behaviour the package doesn't offer (e.g. "find village by postal code" returning multiple matches), add it as a scope on your swapped model — it survives package upgrades, unlike patching vendor code.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource