Laravel Packages

Attach Addresses to Any Eloquent Model with Laravel Addressable

Published
Attach Addresses to Any Eloquent Model with Laravel Addressable image

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 POINT column 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_address accessor 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 address
echo $user->billingAddress->display_address;
// "221B Baker Street - NW1 6XE - London - England - GB"
 
// All shipping addresses
foreach ($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 km
Address::query()->withinRadius($origin, 3_000)->get();
 
// Five closest billing addresses
Address::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.

Yannick Lyn Fatt photo

Staff Writer at Laravel News and Full stack web developer.

Sponsored

masteringlaravel logo
Laravel Code Review

Get expert guidance in a few days with a Laravel code review

Visit Laravel Code Review

The latest

View all →
Laravel Read Replicas: Prevent Stale Reads After a Write image

Laravel Read Replicas: Prevent Stale Reads After a Write

Read article
Screenshot: Capture Images and PDFs with PHP and Laravel image

Screenshot: Capture Images and PDFs with PHP and Laravel

Read article
Reset Sticky Database Connections Between Laravel Queue Jobs image

Reset Sticky Database Connections Between Laravel Queue Jobs

Read article
PHP Security Releases Fix Two Critical RCE Vulnerabilities image

PHP Security Releases Fix Two Critical RCE Vulnerabilities

Read article
Prevent Stale Database Reads in Laravel 13.38 image

Prevent Stale Database Reads in Laravel 13.38

Read article
Critical PHP Security Release Coming Tomorrow (October 21) image

Critical PHP Security Release Coming Tomorrow (October 21)

Read article