Code Examples
Last updated: 2026-03-22
Practical examples for common use cases with Filament Address Pro.
Table of Contents
- Basic Usage
- Working with Multiple Models
- Custom Forms
- Programmatic Address Management
- Address Verification
- Geocoding Integration
- International Addresses
- Advanced Patterns
Basic Usage
Adding Addresses to a User Model
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Viewflex\FilamentAddress\Concerns\HasAddresses;
class User extends Authenticatable
{
use HasAddresses;
// The trait provides:
// - addresses() relationship
// - primaryAddress() method
// - setPrimaryAddress($address) method
}Using the Relation Manager in a Resource
<?php
namespace App\Filament\Resources;
use App\Models\User;
use Filament\Resources\Resource;
use Viewflex\FilamentAddress\Filament\RelationManagers\AddressesRelationManager;
class UserResource extends Resource
{
protected static ?string $model = User::class;
public static function getRelations(): array
{
return [
AddressesRelationManager::class,
];
}
// ... rest of resource
}Creating an Address Manually
use App\Models\User;
$user = User::find(1);
$address = $user->addresses()->create([
'country_code' => 'US',
'address_line_1' => '1600 Pennsylvania Avenue NW',
'locality' => 'Washington',
'administrative_area' => 'DC',
'postal_code' => '20500',
'is_primary' => true,
]);Working with Multiple Models
Customer Model with Addresses
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Viewflex\FilamentAddress\Concerns\HasAddresses;
class Customer extends Model
{
use HasAddresses;
protected $fillable = [
'name',
'email',
'phone',
];
/**
* Get the primary shipping address.
*/
public function shippingAddress()
{
return $this->primaryAddress();
}
/**
* Get the billing address (if different from shipping).
*/
public function billingAddress()
{
return $this->addresses()
->where('address_type', 'billing')
->first();
}
}Company Model with Multiple Locations
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Viewflex\FilamentAddress\Concerns\HasAddresses;
class Company extends Model
{
use HasAddresses;
protected $fillable = [
'name',
'registration_number',
'industry',
];
/**
* Get the headquarters address.
*/
public function headquarters()
{
return $this->addresses()
->where('is_primary', true)
->first();
}
/**
* Get all office locations.
*/
public function offices()
{
return $this->addresses()
->where('address_type', 'office')
->get();
}
/**
* Get warehouses.
*/
public function warehouses()
{
return $this->addresses()
->where('address_type', 'warehouse')
->get();
}
}Property Listing with Location
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Viewflex\FilamentAddress\Concerns\HasAddresses;
class Property extends Model
{
use HasAddresses;
protected $fillable = [
'title',
'description',
'price',
'bedrooms',
'bathrooms',
];
protected $casts = [
'price' => 'decimal:2',
];
/**
* Get the property address.
*/
public function location()
{
return $this->addresses()->first();
}
/**
* Scope properties near coordinates.
*/
public function scopeNearby($query, float $lat, float $lon, int $radiusKm = 10)
{
// Haversine formula for distance calculation
return $query->whereHas('addresses', function ($q) use ($lat, $lon, $radiusKm) {
$q->selectRaw("
*, (6371 * acos(cos(radians(?))
* cos(radians(lat))
* cos(radians(lon) - radians(?))
+ sin(radians(?))
* sin(radians(lat)))) AS distance
", [$lat, $lon, $lat])
->havingRaw("distance < ?", [$radiusKm]);
});
}
}Custom Forms
Inline Address Form in a Page
<?php
namespace App\Filament\Pages;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Pages\Page;
use Viewflex\FilamentAddress\Filament\Schemas\AddressForm;
class CreateShipment extends Page
{
protected static string $view = 'filament.pages.create-shipment';
public ?array $data = [];
public function mount(): void
{
$this->form->fill();
}
public function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Section::make('Shipment Details')
->schema([
Forms\Components\TextInput::make('tracking_number')
->required(),
Forms\Components\DatePicker::make('ship_date')
->required(),
]),
Forms\Components\Section::make('Destination Address')
->description('Where should we ship this package?')
->schema(AddressForm::schema())
->columns(2),
])
->statePath('data');
}
public function create(): void
{
$data = $this->form->getState();
// Process shipment with address data
// $data contains all address fields from AddressForm
}
}Address Form in an Action Modal
Drop the full address form - map search, dynamic dropdowns - into any Filament Action modal with a single spread:
<?php
namespace App\Filament\Pages;
use Filament\Actions\Action;
use Filament\Pages\Page;
use Viewflex\FilamentAddress\Concerns\HandlesMapSelection;
use Viewflex\FilamentAddress\Filament\Schemas\AddressForm;
class CustomerProfile extends Page
{
use HandlesMapSelection;
// Required for Action forms with live updates (map selection, dynamic dropdowns)
public array $data = [];
protected string $view = 'filament.pages.customer-profile';
protected function getActions(): array
{
return [
Action::make('addAddress')
->label('Add Address')
->icon('heroicon-o-plus')
->color('primary')
->modalHeading('Add Address')
->modalWidth('5xl')
->form([
...AddressForm::schema(),
])
->action(function (array $data) {
$this->record->addresses()->create($data);
}),
];
}
}HandlesMapSelection automatically routes map place selections into the active modal form, no extra wiring needed.
Custom Address Form with Additional Fields
<?php
namespace App\Filament\Forms;
use Viewflex\FilamentAddress\Filament\Schemas\AddressForm as BaseAddressForm;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\Section;
class ExtendedAddressForm extends BaseAddressForm
{
public static function schema(): array
{
$baseSchema = parent::schema();
// Add custom fields at the end
$baseSchema[] = Section::make('Additional Information')
->schema([
TextInput::make('delivery_contact_name')
->label('Contact Name')
->helperText('Person to contact for delivery')
->maxLength(255),
TextInput::make('delivery_contact_phone')
->label('Contact Phone')
->tel()
->maxLength(50),
Textarea::make('delivery_instructions')
->label('Delivery Instructions')
->helperText('Gate codes, special instructions, etc.')
->maxLength(500)
->rows(3)
->columnSpanFull(),
Toggle::make('is_residential')
->label('Residential Address')
->default(true)
->helperText('Uncheck for commercial addresses'),
Toggle::make('is_po_box')
->label('PO Box Address')
->helperText('Check if this is a PO Box'),
])
->columns(2);
return $baseSchema;
}
}Programmatic Address Management
Creating Addresses from API Data
use App\Models\Customer;
use Viewflex\FilamentAddress\Services\GeocodingService;
class AddressImportService
{
public function __construct(
private GeocodingService $geocoder
) {}
public function importFromApi(array $customerData): void
{
$customer = Customer::create([
'name' => $customerData['name'],
'email' => $customerData['email'],
]);
// Import addresses
foreach ($customerData['addresses'] as $addressData) {
$this->createAddress($customer, $addressData);
}
}
private function createAddress(Customer $customer, array $data): void
{
// Geocode if coordinates not provided
if (empty($data['lat']) || empty($data['lon'])) {
$fullAddress = "{$data['street']}, {$data['city']}, {$data['state']} {$data['zip']}";
$result = $this->geocoder->geocode($fullAddress, $data['country_code']);
if ($result['success']) {
$data = array_merge($data, $result['components']);
$data['lat'] = $result['lat'];
$data['lon'] = $result['lon'];
}
}
$customer->addresses()->create([
'country_code' => $data['country_code'],
'address_line_1' => $data['address_line_1'] ?? $data['street'],
'locality' => $data['city'],
'administrative_area' => $data['state'],
'postal_code' => $data['zip'],
'lat' => $data['lat'] ?? null,
'lon' => $data['lon'] ?? null,
'is_primary' => $data['is_primary'] ?? false,
]);
}
}Setting Primary Address with Validation
use App\Models\Customer;
class AddressService
{
public function setPrimaryAddress(Customer $customer, int $addressId): bool
{
$address = $customer->addresses()->find($addressId);
if (!$address) {
return false;
}
// Unset current primary
$customer->addresses()->update(['is_primary' => false]);
// Set new primary
$address->update(['is_primary' => true]);
return true;
}
public function ensureHasPrimary(Customer $customer): void
{
// If no primary address, make the first one primary
if (!$customer->primaryAddress() && $customer->addresses()->count() > 0) {
$customer->addresses()->first()->update(['is_primary' => true]);
}
}
}Bulk Address Updates
use App\Models\Address;
use Viewflex\FilamentAddress\Models\CountrySubdivision;
class SubdivisionMigrationService
{
/**
* Migrate addresses from text-based subdivisions to ID-based.
*/
public function migrateToSubdivisionIds(): void
{
Address::whereNotNull('administrative_area')
->whereNull('administrative_area_id')
->chunk(100, function ($addresses) {
foreach ($addresses as $address) {
$this->linkSubdivision($address);
}
});
}
private function linkSubdivision(Address $address): void
{
// Find subdivision by name
$subdivision = CountrySubdivision::where('country_code', $address->country_code)
->where('name', $address->administrative_area)
->where('level', 1)
->first();
if ($subdivision) {
$address->update([
'administrative_area_id' => $subdivision->id,
]);
}
}
}Address Verification
Manual Verification
use Viewflex\FilamentAddress\Services\Verification\AddressVerificationService;
class ManualVerificationExample
{
public function __construct(
private AddressVerificationService $verifier
) {}
public function verifyAddress(array $addressData): array
{
$result = $this->verifier->verify([
'address_line_1' => $addressData['street'],
'locality' => $addressData['city'],
'administrative_area' => $addressData['state'],
'postal_code' => $addressData['zip'],
'country_code' => 'US',
]);
if ($result->isSuccessful()) {
return [
'verified' => true,
'provider' => $result->getProvider(),
'address_line_1' => $result->getAddressLine1(),
'city' => $result->getCity(),
'state' => $result->getState(),
'postal_code' => $result->getPostalCode(),
'metadata' => $result->getMetadata(),
];
}
return [
'verified' => false,
'error' => $result->getError(),
];
}
}Automatic Verification on Create
use App\Models\Address;
use Viewflex\FilamentAddress\Services\Verification\AddressVerificationService;
class AddressObserver
{
public function __construct(
private AddressVerificationService $verifier
) {}
public function created(Address $address): void
{
// Only verify if enabled in config
if (!config('addresses.verification.enabled')) {
return;
}
$result = $this->verifier->verify([
'address_line_1' => $address->address_line_1,
'address_line_2' => $address->address_line_2,
'locality' => $address->locality,
'administrative_area' => $address->administrative_area,
'postal_code' => $address->postal_code,
'country_code' => $address->country_code,
]);
if ($result->isSuccessful()) {
$address->markAsVerified($result->getProvider(), $result->getMetadata());
// Optionally update with verified data
$address->update([
'address_line_1' => $result->getAddressLine1() ?? $address->address_line_1,
'locality' => $result->getCity() ?? $address->locality,
'administrative_area' => $result->getState() ?? $address->administrative_area,
'postal_code' => $result->getPostalCode() ?? $address->postal_code,
]);
}
}
}
// Register in AppServiceProvider
use App\Models\Address;
use App\Observers\AddressObserver;
public function boot(): void
{
Address::observe(AddressObserver::class);
}Batch Verification
use App\Models\Address;
use Viewflex\FilamentAddress\Services\Verification\AddressVerificationService;
class BatchVerificationCommand extends Command
{
protected $signature = 'addresses:verify {--country=US}';
protected $description = 'Verify unverified addresses';
public function handle(AddressVerificationService $verifier): void
{
$country = $this->option('country');
$addresses = Address::unverified()
->where('country_code', $country)
->get();
$this->info("Verifying {$addresses->count()} addresses...");
$verified = 0;
$failed = 0;
foreach ($addresses as $address) {
$result = $verifier->verify([
'address_line_1' => $address->address_line_1,
'locality' => $address->locality,
'administrative_area' => $address->administrative_area,
'postal_code' => $address->postal_code,
'country_code' => $address->country_code,
]);
if ($result->isSuccessful()) {
$address->markAsVerified($result->getProvider(), $result->getMetadata());
$verified++;
$this->line("✓ Address {$address->id} verified");
} else {
$failed++;
$this->warn("✗ Address {$address->id} failed: {$result->getError()}");
}
}
$this->info("Complete: {$verified} verified, {$failed} failed");
}
}Geocoding Integration
Forward Geocoding (Address → Coordinates)
use Viewflex\FilamentAddress\Services\GeocodingService;
class PropertyGeocoder
{
public function __construct(
private GeocodingService $geocoder
) {}
public function geocodeProperty(Property $property): void
{
$address = $property->location();
if (!$address) {
return;
}
$fullAddress = implode(', ', array_filter([
$address->address_line_1,
$address->locality,
$address->administrative_area,
$address->postal_code,
]));
$result = $this->geocoder->geocode($fullAddress, $address->country_code);
if ($result['success']) {
$address->update([
'lat' => $result['lat'],
'lon' => $result['lon'],
]);
// Optionally update with verified components
if ($result['components']['is_verified']) {
$address->update([
'is_verified' => true,
'verification_provider' => $result['components']['verification_provider'],
'verified_at' => $result['components']['verified_at'],
]);
}
}
}
}Reverse Geocoding (Coordinates → Address)
use Viewflex\FilamentAddress\Services\GeocodingService;
class LocationService
{
public function __construct(
private GeocodingService $geocoder
) {}
public function createAddressFromCoordinates(
Model $model,
float $lat,
float $lon
): ?Address {
$result = $this->geocoder->reverseGeocode($lat, $lon);
if (!$result['success']) {
return null;
}
$components = $result['components'];
return $model->addresses()->create([
'country_code' => $components['country_code'],
'address_line_1' => $components['address_line_1'],
'locality' => $components['city'],
'administrative_area' => $components['state'],
'postal_code' => $components['postal_code'],
'lat' => $lat,
'lon' => $lon,
'is_verified' => $components['is_verified'],
'verification_provider' => $components['verification_provider'],
]);
}
}Finding Nearby Locations
use App\Models\Store;
class StoreLocatorService
{
public function findNearestStore(float $lat, float $lon, int $limit = 5): Collection
{
return Store::select('stores.*')
->join('addresses', function ($join) {
$join->on('addresses.addressable_id', '=', 'stores.id')
->where('addresses.addressable_type', Store::class);
})
->selectRaw("
(6371 * acos(
cos(radians(?))
* cos(radians(addresses.lat))
* cos(radians(addresses.lon) - radians(?))
+ sin(radians(?))
* sin(radians(addresses.lat))
)) AS distance
", [$lat, $lon, $lat])
->whereNotNull('addresses.lat')
->whereNotNull('addresses.lon')
->orderBy('distance')
->limit($limit)
->get();
}
}International Addresses
Japanese Address Example
use App\Models\Customer;
$customer = Customer::find(1);
$customer->addresses()->create([
'country_code' => 'JP',
'address_line_1' => '4-chōme-2-8 Shibakōen', // Premise number + street
'dependent_locality' => 'Shibakōen', // Sublocality level 1
'locality' => 'Minato City', // City
'administrative_area' => 'Tokyo', // Prefecture
'postal_code' => '105-0011',
'is_primary' => true,
]);South Korean Address Example
use App\Models\Company;
$company = Company::find(1);
$company->addresses()->create([
'country_code' => 'KR',
'address_line_1' => '123', // Premise
'dependent_locality' => 'Myeong-dong', // Sublocality level 4
'locality' => 'Jung-gu', // Sublocality level 3
'administrative_area' => 'Seoul', // City
'postal_code' => '04536',
'is_primary' => true,
]);UK Address Example
use App\Models\User;
$user = User::find(1);
$user->addresses()->create([
'country_code' => 'GB',
'address_line_1' => '10 Downing Street',
'locality' => 'London',
'postal_code' => 'SW1A 2AA',
'is_primary' => true,
]);Canadian Address Example (Bilingual)
use App\Models\Customer;
$customer = Customer::find(1);
$customer->addresses()->create([
'country_code' => 'CA',
'address_line_1' => '111 Wellington Street',
'locality' => 'Ottawa',
'administrative_area' => 'ON', // Ontario
'postal_code' => 'K1A 0A6',
'is_primary' => true,
]);Advanced Patterns
Address Factory for Testing
<?php
namespace Database\Factories;
use App\Models\Address;
use Illuminate\Database\Eloquent\Factories\Factory;
class AddressFactory extends Factory
{
protected $model = Address::class;
public function definition(): array
{
return [
'country_code' => 'US',
'address_line_1' => $this->faker->streetAddress(),
'locality' => $this->faker->city(),
'administrative_area' => $this->faker->stateAbbr(),
'postal_code' => $this->faker->postcode(),
'lat' => $this->faker->latitude(),
'lon' => $this->faker->longitude(),
'is_primary' => false,
];
}
public function primary(): static
{
return $this->state(fn (array $attributes) => [
'is_primary' => true,
]);
}
public function verified(): static
{
return $this->state(fn (array $attributes) => [
'is_verified' => true,
'verification_provider' => 'usps',
'verified_at' => now(),
]);
}
public function japanese(): static
{
return $this->state(fn (array $attributes) => [
'country_code' => 'JP',
'address_line_1' => '4-chōme-2-8 Shibakōen',
'dependent_locality' => 'Shibakōen',
'locality' => 'Minato City',
'administrative_area' => 'Tokyo',
'postal_code' => '105-0011',
]);
}
}Repository Pattern
<?php
namespace App\Repositories;
use App\Models\Address;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
class AddressRepository
{
public function findByModel(Model $model): Collection
{
return $model->addresses;
}
public function getPrimaryAddress(Model $model): ?Address
{
return $model->primaryAddress();
}
public function create(Model $model, array $data): Address
{
return $model->addresses()->create($data);
}
public function update(Address $address, array $data): bool
{
return $address->update($data);
}
public function delete(Address $address): bool
{
return $address->delete();
}
public function setPrimary(Address $address): bool
{
// Unset other primary addresses
$address->addressable->addresses()->update(['is_primary' => false]);
return $address->update(['is_primary' => true]);
}
public function findVerified(Model $model): Collection
{
return $model->addresses()->verified()->get();
}
public function findUnverified(Model $model): Collection
{
return $model->addresses()->unverified()->get();
}
}Event-Driven Address Updates
<?php
namespace App\Events;
use App\Models\Address;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class AddressVerified
{
use Dispatchable, SerializesModels;
public function __construct(
public Address $address,
public string $provider,
public array $metadata
) {}
}
// Listener
namespace App\Listeners;
use App\Events\AddressVerified;
use Illuminate\Support\Facades\Notification;
use App\Notifications\AddressVerificationComplete;
class NotifyAddressVerification
{
public function handle(AddressVerified $event): void
{
if ($event->address->addressable instanceof User) {
$event->address->addressable->notify(
new AddressVerificationComplete($event->address)
);
}
}
}Custom Address Management Resource
The package ships AddressResource as part of getResources() - a fully functional standalone table listing all addresses across all entities, with search, filters, and import/export. Register it with two lines in your panel provider (see PANEL-SETUP.md).
If you need a customized version - different navigation group, app-specific entity type labels, extra columns, or a trimmed-down filter set - here's a starting point to build your own:
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\AddressResource\Pages;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\Filter;
use Filament\Tables\Actions\EditAction;
use Filament\Tables\Actions\DeleteAction;
use Illuminate\Database\Eloquent\Builder;
use Viewflex\FilamentAddress\Models\Address;
use Viewflex\FilamentAddress\Models\Country;
class AddressResource extends Resource
{
protected static ?string $model = Address::class;
protected static ?string $navigationIcon = 'heroicon-o-map-pin';
protected static ?string $navigationGroup = 'Addressing';
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('addressable_type')
->label('Entity Type')
->badge()
->formatStateUsing(fn ($state) => class_basename($state)),
TextColumn::make('addressable.name')
->label('Owner')
->searchable(),
TextColumn::make('address_line_1')
->searchable()
->limit(30),
TextColumn::make('locality')
->label('City')
->searchable(),
TextColumn::make('administrative_area')
->label('State/Province')
->toggleable(),
TextColumn::make('country_code')
->badge()
->color('primary'),
IconColumn::make('is_verified')
->boolean()
->trueIcon('heroicon-o-shield-check')
->falseIcon('heroicon-o-x-circle')
->trueColor('success')
->falseColor('danger'),
TextColumn::make('verification_provider')
->badge()
->color(fn ($state) => match($state) {
'usps' => 'success',
'google' => 'info',
default => 'gray',
})
->toggleable(),
IconColumn::make('is_primary')
->boolean()
->trueColor('warning')
->toggleable(),
TextColumn::make('created_at')
->dateTime()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
SelectFilter::make('country_code')
->options(Country::pluck('name', 'country_code'))
->searchable(),
SelectFilter::make('is_verified')
->label('Verification Status')
->options([
'1' => 'Verified',
'0' => 'Unverified',
]),
SelectFilter::make('verification_provider')
->options([
'usps' => 'USPS',
'google' => 'Google',
]),
SelectFilter::make('addressable_type')
->label('Entity Type')
->options([
'App\\Models\\User' => 'Users',
'App\\Models\\Customer' => 'Customers',
// Add your own model types here
]),
Filter::make('is_primary')
->query(fn (Builder $query) => $query->where('is_primary', true))
->label('Primary Only'),
])
->actions([
EditAction::make(),
DeleteAction::make(),
]);
}
public static function getPages(): array
{
return [
'index' => Pages\ListAddresses::route('/'),
'edit' => Pages\EditAddresses::route('/{record}/edit'),
];
}
}Create the corresponding page classes with php artisan make:filament-resource Address --generate then remove the auto-generated form/table and replace with the above, or build the page classes manually.
Next Steps
- Review USER-GUIDE.md for comprehensive documentation
- Check API-REFERENCE.md for detailed API docs
- Explore CUSTOMIZATION_GUIDE.md for UI customization
- See INSTALLATION.md for setup instructions