Library / SDK
commerceguys/addressing avatar
commerceguys/addressing

commerceguys/addressing: country-aware postal addresses in PHP

A PHP addressing library, powered by CLDR and Google's address data.

970 stars152 forksPHPMIT

At a glance

What is it?
commerceguys/addressing is a PHP 8 library for manipulating postal addresses, with country lists translated into over 150 locales, address formats for more than 200 countries and postal label formatting that matches what carriers expect. It is aimed at e-commerce and billing teams that have to print an address correctly for every destination.
Who is it for?
Adopt commerceguys/addressing when you need to collect, validate and print addresses for many countries without hardcoding per country rules, especially if you already ship to Brazil, Japan or anywhere with non latin scripts. Skip it if you need address verification against a postal database or geocoding, since the library works from format and subdivision data rather than from validating a real deliverable address.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 14 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem is that postal rules are per country

A checkout form with a single set of fields is wrong for most of the world. The order of lines changes, some countries require a dependent locality between the city and the state, postal codes are validated by different patterns, and the country itself has to be written in the right language for the destination postal service.

commerceguys/addressing is a PHP 8.0 and later addressing library that manipulates postal addresses, and the README describes its purpose as identifying a precise recipient location for shipping or billing purposes. The feature list is a catalogue of exactly those variations: countries with translations for over 150 locales powered by CLDR v48, address formats for over 200 countries, subdivisions for 62 countries, both latin and local subdivision names where relevant, formatting in HTML or plain text, validation through symfony/validator, and zones.

The scope is deliberately about representation rather than delivery. The library knows what an address should look like for a given country, and that is a different job from knowing whether a parcel will actually reach the person named there.

Two design choices make this usable in an application rather than a script. The address interface makes no assumptions about mutability, so the implementing application can extend the interface with setters, implement a value object with PSR-7 style with mutators, or use an address builder. A default address value object is provided as an example, and it can be mapped by Doctrine, preferably as an embeddable.

Field names follow the OASIS xAL standard

The data model is defined in terms of the OASIS eXtensible Address Language, xAL, standard, which is why the field list looks the way it does.

The address interface has getters for country code, administrative area, locality, dependent locality, postal code, sorting code, three address lines, organization, given name, additional name for middle name or patronymic, and family name. That covers the cases a single Western first name and last name model misses, such as the patronymic field and the sorting code that some postal systems require.

Subdivisions are hierarchical and can have up to three levels, administrative area, then locality, then dependent locality. Each subdivision carries a subdivision code used on a parcel or envelope, for example CA for California, a subdivision name shown to the user in a dropdown, a local code and name if the country uses a non latin script, for example Cyrillic in Russia, and a postal code pattern if it differs from the one on the address format.

The address format is a separate object that answers the questions an application actually needs. It says which fields are used and in which order, which are required, which need to be uppercased for actual mailing to facilitate automated sorting, the labels for the administrative area, locality, dependent locality and postal code, and the regular expression pattern for validating postal codes.

Keeping the label separate from the code is the useful part. The same field is a state in one country, a province in another and a parish in a third, and the format is where that difference is expressed.

Three repositories, one per kind of reference data

The API is built around three repositories, and the example in the README shows how they compose.

php
use CommerceGuys\Addressing\AddressFormat\AddressFormatRepository;
use CommerceGuys\Addressing\Country\CountryRepository;
use CommerceGuys\Addressing\Subdivision\SubdivisionRepository;

$countryRepository = new CountryRepository();
$addressFormatRepository = new AddressFormatRepository();
$subdivisionRepository = new SubdivisionRepository();

// Get the country list (countryCode => name), in French.
$countryList = $countryRepository->getList('fr-FR');

// Get the country object for Brazil.
$brazil = $countryRepository->get('BR');
echo $brazil->getThreeLetterCode(); // BRA
echo $brazil->getName(); // Brazil
echo $brazil->getCurrencyCode(); // BRL
print_r($brazil->getTimezones());

The country repository gives you a translated list for a locale, a single country object, and all of them at once. The country object carries the country name, the numeric and three letter codes, the official currency code when known, and the timezones the country spans, which is the kind of detail a shipping calculator needs and a hardcoded array never has.

The subdivision repository walks the hierarchy:

php
// Get the address format for Brazil.
$addressFormat = $addressFormatRepository->get('BR');

// Get the subdivisions for Brazil.
$states = $subdivisionRepository->getAll(['BR']);
foreach ($states as $state) {
    $municipalities = $state->getChildren();
}

// Get the subdivisions for Brazilian state Ceará.
$municipalities = $subdivisionRepository->getAll(['BR', 'CE']);
foreach ($municipalities as $municipality) {
    echo $municipality->getName();
}

Passing a path of country and subdivision codes returns the children at that level, so a cascading dropdown is a natural fit: country, then its administrative areas, then the municipalities under one of them.

Two formatters with different jobs

The formatter layer separates what an address looks like on a screen from what a label looks like in a mail sorting system.

The DefaultFormatter formats an address for display and always adds the localized country name:

php
use CommerceGuys\Addressing\Address;
use CommerceGuys\Addressing\Formatter\DefaultFormatter;
use CommerceGuys\Addressing\AddressFormat\AddressFormatRepository;
use CommerceGuys\Addressing\Country\CountryRepository;
use CommerceGuys\Addressing\Subdivision\SubdivisionRepository;

$addressFormatRepository = new AddressFormatRepository();
$countryRepository = new CountryRepository();
$subdivisionRepository = new SubdivisionRepository();
$formatter = new DefaultFormatter($addressFormatRepository, $countryRepository, $subdivisionRepository);
// Options passed to the constructor or format() allow turning off
// html rendering, customizing the wrapper element and its attributes.

$address = new Address();

The address itself is immutable in the value object, built with with style calls:

php
$address = $address
    ->withCountryCode('US')
    ->withAdministrativeArea('CA')
    ->withLocality('Mountain View')
    ->withAddressLine1('1098 Alta Ave');

echo $formatter->format($address);

The output wraps each part in a span with a class such as `address-line1`, `locality`, `administrative-area` and `country`, which is what makes the result usable for styling, and the options allow turning off HTML rendering or customising the wrapper element and its attributes.

The PostalLabelFormatter is the other half. It takes care of uppercasing fields where required by the format to facilitate automated mail sorting, and it needs the origin country code so it can tell domestic mail from international mail. For domestic mail the country name is not displayed at all. For international mail the postal code is prefixed with the destination's postal code prefix, and the country name is added in both the current locale and in English, which the README says matches the recommendation given by the Universal Postal Union to avoid difficulties in countries of transit.

That last rule is the kind of detail most applications get wrong by hand, and it is a good argument for using the library instead of a formatter of your own.

Validation plugs into symfony/validator

Validation is listed as a feature and it is delivered through symfony/validator rather than through a bespoke rule set. The mechanism follows from the data model: the address format carries the regular expression pattern for validating postal codes, and a subdivision can carry its own postal code pattern when it differs from the one on the format.

The practical consequence is that a postal code check is data driven. Change the target country and the rule changes with it, including for countries that use a postal code pattern only within certain regions.

What the library does not do is verify that an address is real. Nothing in the README describes postal database lookups, geocoding or a deliverability check, and there is no verification service referenced. Validation here means the shape of the address is consistent with what the country expects, not that a courier can find the recipient.

That distinction is worth keeping clear when you plan a checkout. A form that accepts only well formed addresses still lets a customer type a street that does not exist, and the failure will surface later as a failed delivery rather than as a rejected form. If you need to know an address is deliverable, that is a separate service, and the README points to a sibling project for related internationalisation work, commerceguys/intl, which covers CLDR-powered languages, currencies and number formatting.

The data was generated from Google, then owned

The provenance of the reference data is stated plainly in the README. Address formats and subdivisions were initially generated from Google's Address Data Service, and are now owned and maintained by the library.

That sentence carries a governance decision. Using Google's address data service as a source gave the project an accurate, broad starting dataset covering formats and subdivisions for many countries, and taking ownership means the data no longer depends on that service continuing to exist or to stay reachable.

The cost of ownership is that the project now carries the maintenance. The country list and translations come from CLDR, at version 48 in the README, so the translated country names follow the Unicode consortium's release cycle rather than the library's own. The formats and subdivisions are the library's own responsibility, which is the part that needs attention when a country changes its postal conventions.

The repository layout supports that data story. There is a `resources/` directory for the bundled data, `scripts/` for the tooling that regenerates or maintains it, `src/` for the code, `tests/` for the test suite, and `composer.json`, `phpcs.xml` and `phpunit.xml` at the top level alongside a build workflow badge.

For anyone judging coverage, the numbers to check are the ones in the README: over 150 locales for country translations, over 200 countries with address formats, but subdivisions for 62 countries. If your shipping destinations include a country with a format but no subdivisions, your dropdown will be flat there.

MIT terms, PHP 8, and a quick release cadence

The licence field on the repository says MIT, which is permissive and lets you use the library in a commercial checkout without obligations beyond keeping the notice. For a dependency that many e-commerce projects will take on, that is the arrangement you want.

The runtime requirement is PHP 8.0 or later, which is the main adoption constraint. On an older codebase this library is effectively off the table unless the application is already on PHP 8.

The release history shows steady work. The tags are v2.3.2 on 2026-10-09, v2.3.1 on 2026-08-23 and v2.3.0 on 2026-08-23, with the last push on 2026-10-09, the same day as the current tag. Two releases in the same afternoon in August suggests the project is comfortable cutting a patch quickly when something needs fixing, and the v2 line has been refined through several point releases.

The upgrade cost is therefore low for most teams. The API is interface driven, so you are likely to depend on `AddressInterface` and the repositories rather than on the concrete value object, which means you can swap the implementation for your own mutable model without abandoning the format and subdivision data.

One practical caveat for a library that ships its own data: an upgrade can change the reference data, not just the code. A new subdivision list or an updated country name is a legitimate reason for a patch release, and if you snapshot address text at checkout, that is where you would notice it.

Editorial conclusion

Adopt commerceguys/addressing when you need to collect, validate and print addresses for many countries without hardcoding per country rules, especially if you already ship to Brazil, Japan or anywhere with non latin scripts. Skip it if you need address verification against a postal database or geocoding, since the library works from format and subdivision data rather than from validating a real deliverable address. Verify first that the countries you ship to have the subdivisions you expect, since subdivision data covers 62 countries, and read the `AddressFormat` for your target country to see which fields it treats as required. The licence field on the repository says MIT, the last push was on 2026-10-09, and the current release is v2.3.2.

Frequently asked questions

How do I install commerceguys/addressing?

The README describes it as a PHP 8.0 and later addressing library powered by CLDR and Google's address data, with a composer.json at the top of the repository. It also points to the sibling project commerceguys/intl for CLDR-powered languages, currencies and number formatting.

How do I format an address for display in commerceguys/addressing?

Use the DefaultFormatter, which is constructed with the address format, country and subdivision repositories, then call `format($address)`. The output wraps each part in a span with a class such as `address-line1` or `locality`, and options allow turning off HTML rendering or customising the wrapper element.

What is the difference between DefaultFormatter and PostalLabelFormatter?

The DefaultFormatter formats an address for display and always adds the localized country name. The PostalLabelFormatter uppercases fields where the format requires it for automated mail sorting, needs the origin country code, omits the country name for domestic mail, and for international mail prefixes the postal code and adds the country name in the current locale and in English.

Which countries have subdivision data in commerceguys/addressing?

The README states that subdivisions, covering administrative areas, localities and dependent localities, are provided for 62 countries, while address formats cover over 200 countries. Subdivisions are hierarchical with up to three levels: administrative area, locality and dependent locality.

Does commerceguys/addressing verify that an address is deliverable?

No. Validation is delivered through symfony/validator using the postal code regular expression pattern carried by the address format, so it checks that the shape of the address matches what the country expects. The README does not describe postal database lookups, geocoding or a deliverability service.

What licence does commerceguys/addressing use?

The licence field on the repository says MIT. The current release is v2.3.2 published on 2026-10-09, with v2.3.1 and v2.3.0 both released on 2026-08-23.

Official sources

  1. commerceguys/addressing on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/commerceguys-addressing.svg)](https://hysenlabs.com/projects/commerceguys-addressing)

Was this useful?