Address types
An address’s type is a lowercase key stored in the type column. Which keys your application allows is configured in addresses.types; it ships the six built-in values of the string-backed enum RoundlyConsulting\Addresses\Enums\AddressType, so an app that never sets it gets exactly these:
| Case | Value | English label |
|---|---|---|
AddressType::Default | default | Default |
AddressType::Billing | billing | Billing |
AddressType::Shipping | shipping | Shipping |
AddressType::Home | home | Home |
AddressType::Work | work | Work |
AddressType::Office | office | Office |
use RoundlyConsulting\Addresses\Enums\AddressType;
AddressType::Office->value; // 'office'
AddressType::Office->label(); // 'Office' ('Kancelárska' under the sk locale)
AddressType::cases(); // all six cases
AddressType::from('billing'); // AddressType::BillingCustom types
List your own keys in config/addresses.php. The list is the whole allow-list — a built-in you leave out is refused like an unknown key. A key is lowercase letters, digits, - and _, at most 255 characters; input is trimmed and lower-cased first, so ' Registered ' is registered. A comma-separated string (for example from an env value) is a list too, and null, blank or [] means the six built-ins. A malformed list throws InvalidConfigurationException naming addresses.types:
// config/addresses.php — the whole allow-list: a built-in you leave out is refused too
'types' => [
'default',
'billing',
'registered' => 'Registered office', // a type and its label
'postal', // labelled "Postal"
'operating' => ['label' => 'Operating site'],
],Every write path takes an AddressType case or a key and refuses one that is not configured with InvalidAddressTypeException. Reads take a key as well and are not checked, so rows of a type you later remove from the list stay reachable. The type column is a plain string, so no migration is needed:
use RoundlyConsulting\Addresses\Address;
use RoundlyConsulting\Addresses\DataTransferObjects\AddressData;
use RoundlyConsulting\Addresses\Facades\Addresses;
// every write takes an AddressType case or a configured key — anything else throws
Addresses::for($company)->new()->type('registered')->/* … */->save();
Addresses::for($company)->add(AddressData::make(/* … */, type: 'postal'));
$company->createAddress(/* … */, type: 'operating');
$address->type = 'postal'; // checked too
Address::factory()->ofType('registered')->create();
// reads take a key as well and are not checked, so rows of a removed type stay reachable
Addresses::for($company)->ofType('registered');
Addresses::for($company)->primary('registered');
$company->primaryAddress('registered');
Address::query()->where('type', 'registered'); // scopeOfType() stays enum-only for nowReading the type
$address->type reads as the AddressType case for a built-in key and as the stored string for a custom one; typeKey() is the string either way, and toArray() and AddressResource always render the key. Use typeKey() whenever you only need the stored value:
use RoundlyConsulting\Addresses\Enums\AddressType;
use RoundlyConsulting\Addresses\Facades\Addresses;
$billing->type; // AddressType::Billing — a built-in reads as the case
$registered->type; // 'registered' — a custom type reads as the key
$registered->typeKey(); // 'registered' — always the stored string
$registered->typeLabel(); // 'Registered office'
Addresses::types(); // ['default', 'billing', 'registered', 'postal', 'operating']
Addresses::typeLabel('registered'); // 'Registered office'
Addresses::typeLabel('head_office'); // 'Head Office' — a headline of the key
AddressType::Billing->label(); // 'Billing' ('Fakturačná' under the sk locale)Labels
Addresses::typeLabel(), $address->typeLabel() and AddressType::label() resolve, in order: the label configured in addresses.types (it also overrides a built-in), the addresses::types.<key> translation (English and Slovak ship for the six built-ins, so labels follow the app locale), then a headline of the key (head_office → Head Office). Publish the translations to add a language or translate a custom key:
php artisan vendor:publish --tag="addresses-translations"From request input
Validate with Rules\ValidAddressType: it passes exactly what the package would accept — a configured key (trimmed, case-insensitive) or an AddressType case — and its message is the addresses::validation.type translation. To resolve an untrusted string yourself, Support\AddressTypes::resolve() returns the case or the key and throws InvalidAddressTypeException for an unconfigured type:
use RoundlyConsulting\Addresses\Rules\ValidAddressType;
use RoundlyConsulting\Addresses\Support\AddressTypes;
// validate: passes exactly the types configured in addresses.types
$request->validate(['type' => ['required', new ValidAddressType]]);
// …or resolve an untrusted string yourself
$type = AddressTypes::resolve($input); // the case or the key; throws InvalidAddressTypeExceptionSelect options
Addresses::types() lists the configured keys and Addresses::typeLabel() labels each one in the current locale — a ready-made type picker:
use RoundlyConsulting\Addresses\Facades\Addresses;
$options = collect(Addresses::types())
->mapWithKeys(fn (string $type) => [$type => Addresses::typeLabel($type)])
->all();
// ['default' => 'Default', 'billing' => 'Billing', 'registered' => 'Registered office', …]Built-in only, for now
Address::scopeOfType() and the Addressable contract still take an AddressType, so existing subclasses and implementations keep compiling; both accept a key from the next major. Until then, query a custom type with Addresses::for($owner)->ofType('registered') or ->where('type', 'registered'). Support\DefaultAddressType::type() returns the default as a case or a key; the older resolve() throws InvalidAddressTypeException when the default is a custom type.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.