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 →
Agent Run Observability in Laravel AI SDK 0.11 image

Agent Run Observability in Laravel AI SDK 0.11

Read article
Debounced Queued Event Listeners in Laravel image

Debounced Queued Event Listeners in Laravel

Read article
Statamic Mailables Viewer Previews Laravel Emails in the Control Panel image

Statamic Mailables Viewer Previews Laravel Emails in the Control Panel

Read article
Laravel Tackle: Run an AI Coding Agent in Your Laravel App image

Laravel Tackle: Run an AI Coding Agent in Your Laravel App

Read article
Queue::forward(): Reroute Laravel Queues in One Place image

Queue::forward(): Reroute Laravel Queues in One Place

Read article
Laravel Read-Through Filesystem: Lazy Storage Migration image

Laravel Read-Through Filesystem: Lazy Storage Migration

Read article