Laravel Addressable by Luca Longo gives any Eloquent model a polymorphic addresses relation, dedicated billing and shipping traits, a free-form JSON meta column, and geospatial distance queries — all wired up through a single migration and a trait.
Features
- Polymorphic by default: attach addresses to any model without extra join tables
- Billing and shipping traits: shorthand relations and helper methods scoped per address type
- Primary address toggling:
markPrimary()/unmarkPrimary()with scoped events dispatched on change - Geospatial support: store a
POINTcolumn and query by radius, distance, or nearest neighbors (requires MySQL 8+, MariaDB 10.5+, or PostgreSQL with PostGIS) - JSON meta column: attach arbitrary extra data (phone, floor, delivery notes) to any address
- Configurable display format: a
display_addressaccessor built from a template you control in config
Install the package and publish the migration:
composer require masterix21/laravel-addressable php artisan vendor:publish --provider="Masterix21\Addressable\AddressableServiceProvider" --tag="migrations" php artisan migrate
Add the appropriate trait to your model. HasAddresses gives you a general-purpose addresses relation on any model:
use Masterix21\Addressable\Models\Concerns\HasAddresses; class Store extends Model{ use HasAddresses;} $store->addAddress([ 'street_address1' => '4 Privet Drive', 'zip' => 'GU26 6HS', 'city' => 'Little Whinging', 'state' => 'Surrey', 'country' => 'GB',]); $store->addresses; // MorphMany of Address models
HasBillingAddresses and HasShippingAddresses each add typed relations and scoped helpers for models that need to distinguish between address types:
use Masterix21\Addressable\Models\Concerns\HasBillingAddresses;use Masterix21\Addressable\Models\Concerns\HasShippingAddresses; class User extends Model{ use HasBillingAddresses, HasShippingAddresses;}
With those traits in place, you get billingAddress (primary, MorphOne), billingAddresses (all, MorphMany), and the shipping equivalents. Adding an address is a single method call:
$user->addBillingAddress([ 'label' => 'Head Office', 'street_address1' => '221B Baker Street', 'zip' => 'NW1 6XE', 'city' => 'London', 'state' => 'England', 'country' => 'GB',]);
The meta JSON column lets you store anything that doesn't fit the standard fields without touching the schema:
$user->addShippingAddress([ 'street_address1' => '1600 Pennsylvania Ave NW', 'city' => 'Washington', 'state' => 'DC', 'country' => 'US', 'meta' => [ 'phone' => '+1 202 456 1111', 'floor' => 1, 'notes' => 'Leave at reception', ],]); $address->meta['phone']; // '+1 202 456 1111'
The display_address accessor formats an address using a template from config/addressable.php. The default produces strings like "1600 Pennsylvania Ave NW - 20500 - Washington - DC - US", and you can swap the template for anything your UI expects.
// Single addressecho $user->billingAddress->display_address;// "221B Baker Street - NW1 6XE - London - England - GB" // All shipping addressesforeach ($user->shippingAddresses as $address) { echo $address->display_address;}
For applications that need location-aware queries, the package stores coordinates as a spatial POINT column (backed by matanyadaev/laravel-eloquent-spatial) and adds scopes for radius filtering and nearest-neighbour sorting:
use MatanYadaev\EloquentSpatial\Objects\Point; $user->addBillingAddress([ 'street_address1' => '10 Downing Street', 'city' => 'London', 'country' => 'GB', 'coordinates' => new Point(51.5034, -0.1276, config('addressable.srid')),]); $origin = new Point(51.5074, -0.1278, config('addressable.srid')); // Addresses within 3 kmAddress::query()->withinRadius($origin, 3_000)->get(); // Five closest billing addressesAddress::query()->billing()->nearest($origin, 5)->get();
Query scopes for primary(), billing(), and shipping() are fully composable, so you can mix them freely — Address::query()->billing()->primary()->first() does exactly what it says.
You can learn more and view the source code on GitHub.