NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Addresses for Laravel

Fluent builder

Addresses::for($model)->new() and $model->newAddress() both return a PendingAddress. Chain the fields, then save() persists the address and returns it. Country codes are normalised automatically, and an address with no type() gets the configured default_type:

use RoundlyConsulting\Addresses\Enums\AddressType;
use RoundlyConsulting\Addresses\Facades\Addresses;

Addresses::for($payer)->new()
    ->type(AddressType::Office)
    ->name('HQ')
    ->primary()
    ->in('Bratislava')->at('Somewhere 1')->postalCode('81101')->country('sk')
    ->meta(['floor' => 3])
    ->save();

// or from the model
$payer->newAddress()
    ->type(AddressType::Home)
    ->in('Košice')->at('Main 2')->postalCode('04001')->country('SK')
    ->save();

Builder methods

MethodEffect
type(AddressType $type)Address type — without it, the configured default_type.
name(string $name)Optional label, e.g. HQ; a blank name is stored as null.
in(string $city)City — required.
at(string $street)Street line — required.
postalCode(string $postalCode)Postal code — required.
country(string $country)ISO country code — required; normalised on save.
primary(bool $isPrimary = true)Make it the primary address of its type; siblings are demoted on save, in the same transaction.
meta(iterable $meta)Arbitrary extras, wrapped in a Collection and stored as JSON.
save(): AddressChecks the four required fields, adds the address through the owner’s address book and returns it.

Required fields

City, street, postal code and country are required. Calling save() without all four — or with a blank value — throws IncompleteAddressException naming every missing field:

use RoundlyConsulting\Addresses\Exceptions\IncompleteAddressException;

try {
    $customer->newAddress()->in('Bratislava')->save();
} catch (IncompleteAddressException $e) {
    $e->getMessage();
    // 'The address is missing required field(s): [street, postalCode, country].'
}

What save() does

The collected fields pass through AddressData::make() — strings are trimmed, a blank name becomes null, the country is normalised and a missing type becomes default_type — and save() adds the result through the owner’s address book, so Addresses::fake() sees it too. CreateAddressAction inserts the row on the owner’s morph relation and, when primary() was set, promotes it in the same transaction, demoting the other addresses of that type. Then AddressCreated is dispatched, followed by PrimaryAddressChanged for a promotion.

Addresses::for() accepts any Eloquent model — the HasAddresses trait is only needed for the model-side helpers.

From a request

use App\Models\Customer;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use RoundlyConsulting\Addresses\Enums\AddressType;
use RoundlyConsulting\Addresses\Http\Resources\AddressResource;

final class CustomerAddressController
{
    public function store(Request $request, Customer $customer): AddressResource
    {
        $data = $request->validate([
            'type' => ['required', Rule::enum(AddressType::class)],
            'city' => ['required', 'string', 'max:255'],
            'street' => ['required', 'string', 'max:255'],
            'postal_code' => ['required', 'string', 'max:255'],
            'country' => ['required', 'string', 'min:2', 'max:3'],
            'primary' => ['boolean'],
        ]);

        $address = $customer->newAddress()
            ->type(AddressType::from($data['type']))
            ->in($data['city'])
            ->at($data['street'])
            ->postalCode($data['postal_code'])
            ->country($data['country'])            // 'sk' is stored as 'SK'
            ->primary($request->boolean('primary'))
            ->save();

        return AddressResource::make($address);
    }
}

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 crypto

By 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.