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

Laravel Address Laravel Package

yajra/laravel-address

A Laravel package for address management. It provides models, migrations, and helpers for countries, states, and cities, making it easy to attach structured addresses to Eloquent models and build reliable address forms or APIs.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Pin the package major to your Laravel major before anything else. The release train mirrors the framework: v13.x ↔ Laravel 13 (ships the PSGC 1Q 2026 snapshot), v12.x ↔ Laravel 12 (older dataset). Decide the constraint first, otherwise you'll spend an hour reading composer resolution errors that boil down to a version mismatch.

composer require yajra/laravel-address:^13.0

Auto-discovery means there's no provider registration step on modern Laravel.

2. Read three things before writing code: config/address.php, the shipped migrations, then src/Models. That order matters.

  • The config tells you which table names and model classes are bound — this is the supported customization surface.
  • The migrations are the only reliable source of truth for the schema. Confirm whether PSGC codes are stored as first-class columns (not just names), what the foreign keys point at, and whether those FKs are actually indexed.
  • The models tell you the relationship names and — critically — whether the chain Region → Province → City/Municipality → Barangay is mandatory at every level. It is not. NCR and independent cities break the province step.

3. Publish config and migrations in one pass, then migrate.

php artisan vendor:publish --provider="Yajra\Address\AddressServiceProvider"
php artisan migrate

Publish both together. The migrations read table names from config; if you rename tables after migrating you end up with a half-renamed schema and a very confusing debugging session. Check the provider for the actual publish tags (address-config, address-migrations, address-seeders) rather than guessing.

4. Seed on a scratch database and measure it. The dataset is roughly 42k barangays plus regions, provinces, and cities/municipalities. Before you wire it into CI:

DB::disableQueryLog();   // otherwise the query log eats your memory

Time the seed, watch peak memory, watch table size. A row-by-row seeder is fine on your laptop and a production liability in a deploy hook.

5. First use case: the cascading picker. Ship GET /regions → GET /provinces?region_code= → GET /cities?province_code= → GET /barangays?city_code= behind a cache, with server-side chain validation on submit. Get that end-to-end before touching backfills, search, or reporting. It's the 80% use case and it forces you to confront the NCR nullable-province case immediately.

Implementation Patterns

Store the code as the FK, snapshot the name into your own column. This is the single most consequential day-to-day decision. Reference tables get re-seeded on every PSGC publication; invoices, delivery manifests, and official receipts must not silently change spelling because someone ran composer update. So at write time persist both:

$order->update([
    'barangay_code'   => $barangay->code,
    'city_code'       => $barangay->city_code,
    'province_code'   => $barangay->city->province_code, // nullable — NCR
    'region_code'     => $barangay->city->province->region_code ?? $barangay->city->region_code,
    'barangay_name'   => $barangay->name,                // frozen snapshot
    'city_name'       => $barangay->city->name,
]);

Codes give you joins, revalidation, and grouping. The name snapshot gives you an immutable historical record.

Put it behind an AddressResolver contract. It costs an interface and one binding today and saves a re-architecture later — whether you swap to Google Places/Loqate, or the package's release train lags a Laravel version you need.

interface AddressResolver {
    public function regions(): Collection;
    public function provinces(string $regionCode): Collection;
    public function cities(string $provinceCode): Collection;
    public function barangays(string $cityCode): Collection;
    public function resolveChain(string $barangayCode): ?array;
}

Bind an Eloquent implementation. In tests, bind a fake backed by three fixture geographies so your test suite never touches 42k rows.

Validate the chain on the server, derive parents from the leaf. exists:barangays,code alone lets a client POST a Cebu barangay under a Manila city and you'll happily store it. Resolve the barangay once, derive every ancestor code from it server-side, and discard whatever the client claimed about the parents. If you want a rule anyway:

Rule::exists('barangays', 'code')
    ->where(fn ($q) => $q->where('city_code', $request->input('city_code')));

Cache the cascade in tiers, keyed by dataset version. Regions and provinces are effectively static — cache for a day or the process lifetime. Cities (~1.6k rows) a few hours. Barangays per-city, on demand. Key every entry with a dataset version (a psgc_release config value or the package version) so a data refresh invalidates cleanly instead of serving stale barangay lists for a week.

Cache::remember("addr:v{$v}:barangays:{$cityCode}", 3600, fn () =>
    Barangay::where('city_code', $cityCode)->orderBy('name')->get(['code', 'name'])
);

Return lean payloads. Cascade endpoints should never return full Eloquent models with timestamps. select('code', 'name') or an API Resource returning {code, name} keeps the frontend contract stable if you ever swap implementations.

Refresh via idempotent upsert on code, never truncate.

Barangay::upsert($rows, ['code'], ['name', 'city_code', 'updated_at']);

Live orders reference these rows. Never truncate. Retire removed codes with an active/retired_at column so historical records still resolve to something.

Backfill legacy free text by descending specificity. Match barangay → city → province, batch it, and route anything below your confidence threshold to manual review. Barangay fuzzy matching is genuinely hard — duplicate names and near-identical spellings are common. Keep the legacy text columns behind a feature flag until parity is verified.

Group reporting on codes, never names. Demand analysis, coverage gaps, expansion decisions — all group by region_code/province_code. The canonical-key property is most of the package's value; don't throw it away in a GROUP BY name.

Test with a trimmed fixture. Export a handful of regions/cities/barangays, seed that in TestCase. Three or four geography branches — including one NCR city with a null province — exercise the whole cascade.

Gotchas and Tips

Data freshness only arrives through composer update. This is the biggest operational trap. The snapshot is bundled, so a stale dataset throws no errors — the list just quietly decays and surfaces months later as failed deliveries to a barangay that was renamed. Pin the version, add a quarterly "check yajra/laravel-address releases" calendar item, and add a smoke test asserting a few known codes plus row counts.

A data refresh can be welded to a major bump. v13.0.0 shipped Laravel 13 support and the 1Q 2026 publication in the same release. If framework constraints pin you to v12.x, you're also on the older dataset. Diff the constraints and migrations before deciding whether the refresh is worth the framework jump.

NCR has no provinces — design for it explicitly. Metro Manila is region → (districts) → city → barangay with a nullable province step. Independent/chartered cities outside provincial jurisdiction behave the same way. Any UI that assumes four mandatory levels breaks on the country's largest market. Model province_code as nullable and handle the empty-province branch in the resolver, the validation, and the API payload.

"City" means city or municipality. The package labels that level City. Be consistent in your own schema and UI labels — municipalities shoved into a "City" column confuse every future reader and every future query writer.

This is enumeration, not verification. It gives you a correct, canonical list. It does not confirm deliverability, geocodability, or that anyone lives there. When a stakeholder asks for "address validation," be precise: you deliver selection correctness plus structural validation. Rooftop confirmation is a different purchase.

Check the indexes the migrations actually create before benchmarking. Reference-data packages routinely skip FK indexes assuming small tables — wrong at 42k barangays. Add a migration for anything missing on province_code, city_code, and searchable name, then load-test at your target concurrency.

Watch N+1s in eager-loaded cascades. Barangay::with('city.province.region') on a paginated page is fine; the same call inside a loop is not. Resolve codes once client-side and reuse them rather than re-querying per row.

Config model bindings are a first-class extension point — use them. Need delivery_zone_id, geocoordinates from your own geocoder, or an active flag? Extend the package model, repoint the config binding, add your own migration. Do not fork the package.

Trust the schema over the prose. The repo describes itself as a Philippines address lookup while the generic description mentions countries/states/cities. Verify whether regions/provinces/barangays actually exist in your installed version before building against a countries table that may not be there.

Keep provenance visible. The data derives from the PSA's public PSGC publication. Confirm the snapshot's terms and whether attribution is expected, and record the source plus refresh date in your README right next to the dependency.

Budget the recurring upgrade ticket. Adopting this package means adopting a release train that follows Laravel's. Each framework cycle carries a small, predictable upgrade task — the AddressResolver contract is what keeps that task from ever becoming an incident.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource