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

laravelcloud logo
Laravel Cloud

Easily create and manage your servers and deploy your Laravel applications in seconds.

Visit Laravel Cloud

The latest

View all →
CPX: The Composer Package Executor for PHP image

CPX: The Composer Package Executor for PHP

Read article
Laravel AI SDK Adds Human-in-the-Loop Tool Approval image

Laravel AI SDK Adds Human-in-the-Loop Tool Approval

Read article
Pest 5 Released With Test Impact Analysis, Agent Verification, and Evals image

Pest 5 Released With Test Impact Analysis, Agent Verification, and Evals

Read article
Queue-SQL: Run Mass Deletes and Updates Across Parallel Queue Jobs image

Queue-SQL: Run Mass Deletes and Updates Across Parallel Queue Jobs

Read article
Blade Formatting in Laravel Pint image

Blade Formatting in Laravel Pint

Read article
Inertia DevTools Is Now on the Chrome Web Store image

Inertia DevTools Is Now on the Chrome Web Store

Read article