Skip to content

Implementation Patterns Guide

Last updated: 2026-03-22

Purpose: Guide developers through the 4 main ways to implement this package Created: February 15, 2026 Based on: Comprehensive onboarding testing (10 scenarios)


Overview

This package supports 4 distinct implementation patterns. Choose based on your needs:

PatternUse CaseComplexityTime
Pattern 1: Basic (Polymorphic)Standard address storageLow15 min
Pattern 2: Embedded FieldsSingle address per entityMedium30 min
Pattern 3: Custom Extended FormAdd custom fields to addressMedium30 min
Pattern 4: Multiple PanelsMulti-tenant or different access levelsMedium45 min

Best for: Most use cases - multiple addresses per entity with full package features

Step 1: Install Package

bash
composer require viewflex/filament-address-pro

# Publish config
php artisan vendor:publish --tag="filament-address-config"

# Run migrations
php artisan migrate

# Seed country data (REQUIRED)
php artisan db:seed --class="Viewflex\FilamentAddress\Database\Seeders\CountriesDomainSeeder"

Step 2: Configure Environment

env
# .env — two-key setup recommended for production (see CONFIGURATION-GUIDE.md)
GOOGLE_MAPS_SERVER_KEY=your_server_key    # IP-restricted: Geocoding + Static Maps + Address Validation
GOOGLE_MAPS_BROWSER_KEY=your_browser_key  # Domain-restricted: Maps JavaScript + Places APIs
# GOOGLE_MAPS_API_KEY=your_key           # Single-key fallback if above are not set
ADDRESS_VERIFICATION_PROVIDER=auto  # auto, usps, google, or none
USPS_CONSUMER_KEY=your_key  # Optional - for US verification
USPS_CONSUMER_SECRET=your_secret

Step 3: Add Trait to Models

php
// app/Models/Customer.php
use Viewflex\FilamentAddress\Concerns\HasAddresses;

class Customer extends Model
{
    use HasAddresses;
}

Step 4: Add Relation Manager to Resource

php
// app/Filament/Resources/CustomerResource.php
use Viewflex\FilamentAddress\Filament\RelationManagers\AddressesRelationManager;

public static function getRelations(): array
{
    return [
        AddressesRelationManager::class,
    ];
}

Step 5: Register Package Resources (Optional)

Only if you want the standalone Address manager:

php
// app/Providers/Filament/AdminPanelProvider.php
use Viewflex\FilamentAddress\FilamentAddressServiceProvider;

public function panel(Panel $panel): Panel
{
    return $panel
        // ... other config
        ->resources(FilamentAddressServiceProvider::getResources());
}

Step 6: Configure Import Whitelist

php
// config/addresses.php
'import' => [
    'allowed_entity_types' => [
        'App\Models\User',
        'App\Models\Customer',  // Add your entities
        'App\Models\Location',
    ],
],

✅ Done! Test It

  1. Go to Customer resource → Edit → Addresses tab
  2. Click "New Address"
  3. Use map search or type address
  4. Save and verify

Pattern 2: Embedded Address Fields

Best for: Entities that have exactly ONE address (e.g., User profile, Company HQ)

When to Use

  • Entity always has exactly one address
  • Don't need address history
  • Want simpler UI (no relation manager tab)

Step 1-2: Same as Pattern 1

Follow Steps 1-2 from Pattern 1 (install + configure)

Step 3: Create Migration with Address Fields

⚠️ IMPORTANT: Use ulid() for subdivision IDs, NOT unsignedBigInteger()

php
// database/migrations/xxxx_create_companies_table.php
Schema::create('companies', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->nullable();

    // Address fields
    $table->string('country_code', 2)->nullable();
    $table->string('address_line_1')->nullable();
    $table->string('address_line_2')->nullable();
    $table->string('postal_code')->nullable();

    // ⚠️ CRITICAL: Use ulid() not unsignedBigInteger()
    $table->ulid('administrative_area_id')->nullable();
    $table->string('administrative_area')->nullable();
    $table->ulid('locality_id')->nullable();
    $table->string('locality')->nullable();
    $table->ulid('dependent_locality_id')->nullable();
    $table->string('dependent_locality')->nullable();

    $table->decimal('lat', 10, 7)->nullable();
    $table->decimal('lon', 10, 7)->nullable();

    $table->timestamps();
});

Step 4: Embed AddressForm in Resource

php
// app/Filament/Resources/Companies/Schemas/CompanyForm.php
use Viewflex\FilamentAddress\Filament\Schemas\AddressForm;
use Filament\Forms\Components\Section;

public static function configure(Schema $schema): Schema
{
    return $schema->components([
        TextInput::make('name')->required(),
        TextInput::make('email')->email(),

        Section::make('Company Address')
            ->schema(AddressForm::schema())
            ->columns(2),
    ]);
}

Step 5: Add HandlesMapSelection to Edit Page

php
// app/Filament/Resources/Companies/Pages/EditCompany.php
use Viewflex\FilamentAddress\Concerns\HandlesMapSelection;

class EditCompany extends EditRecord
{
    use HandlesMapSelection;

    protected static string $resource = CompanyResource::class;
}

✅ Done! Test It

  1. Create/Edit Company
  2. Fill address fields or use map search
  3. Save and verify all fields populate

Pattern 3: Extended Address Form with Custom Fields

Best for: Need standard address PLUS custom fields (delivery instructions, gate codes, etc.)

Step 1-3: Same as Pattern 1

Follow Steps 1-3 from Pattern 1 (install + configure + add trait)

Step 4: Create Extended Form Class

php
// app/Filament/Schemas/CustomAddressForm.php
namespace App\Filament\Schemas;

use Viewflex\FilamentAddress\Filament\Schemas\AddressForm as BaseAddressForm;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;

class CustomAddressForm extends BaseAddressForm
{
    public static function schema(): array
    {
        return [
            ...parent::schema(),  // All standard address fields

            // Your custom fields
            TextInput::make('delivery_notes')
                ->label('Delivery Instructions')
                ->maxLength(500)
                ->columnSpanFull(),

            TextInput::make('gate_code')
                ->label('Gate/Access Code')
                ->maxLength(50),

            TextInput::make('parking_info')
                ->label('Parking Information')
                ->maxLength(255),
        ];
    }
}

Step 5: Add Custom Fields to Migration

php
// database/migrations/xxxx_add_custom_fields_to_addresses.php
Schema::table('addresses', function (Blueprint $table) {
    $table->text('delivery_notes')->nullable();
    $table->string('gate_code', 50)->nullable();
    $table->string('parking_info')->nullable();
});

Step 6: Add to Address Model Fillable

php
// In your AppServiceProvider or AddressObserver
use Viewflex\FilamentAddress\Models\Address;

Address::mergeFillable([
    'delivery_notes',
    'gate_code',
    'parking_info',
]);

Step 7: Use Custom Form in Resource

php
// app/Filament/Resources/Companies/Schemas/CompanyForm.php
use App\Filament\Schemas\CustomAddressForm;

Section::make('Company Address')
    ->schema(CustomAddressForm::schema())
    ->columns(2),

✅ Done! Test It

  1. Create address with custom form
  2. Fill standard + custom fields
  3. Save and verify custom fields persist

Pattern 4: Multiple Panels

Best for: Multi-tenant apps or different admin/customer panels

Step 1-2: Same as Pattern 1

Follow Steps 1-2 from Pattern 1 (install + configure)

Step 3: Configure Custom Panel

php
// app/Providers/Filament/CustomerPanelProvider.php
public function panel(Panel $panel): Panel
{
    return $panel
        ->id('customer')
        ->path('customer')
        // ... other config
        ->resources(FilamentAddressServiceProvider::getResources());
}

Step 4: Update Package Config

env
# .env - MUST match your panel configuration
ADDRESS_PANEL_ID=customer
ADDRESS_PANEL_PATH=customer

Step 5: Configure Authorization

php
// config/addresses.php
'authorization' => [
    'enabled' => true,

    // Optional: use a custom policy class
    // 'policy' => \App\Policies\AddressPolicy::class,
],

✅ Done! Test It

  1. Access both panels (/admin and /customer)
  2. Verify Address resources appear correctly
  3. Test authorization restrictions

Common Issues & Solutions

Issue: "Data truncated for column 'administrative_area_id'"

Cause: Used unsignedBigInteger instead of ulid()

Fix:

php
// Migration
$table->ulid('administrative_area_id')->change();
$table->ulid('locality_id')->change();
$table->ulid('dependent_locality_id')->change();

Issue: Import fails with "Failed: X"

Cause: Entity type not in whitelist

Fix:

php
// config/addresses.php
'import' => [
    'allowed_entity_types' => [
        'App\Models\User',
        'App\Models\YourModel',  // Add here
    ],
],

Issue: Verification dialog doesn't appear

This is NORMAL! The dialog only shows when there are differences that need your decision. If blur geocoding + verification work seamlessly, you won't see it. This is good UX.

To verify it's working: Check database for is_verified and verification_provider fields.


Testing Your Implementation

Quick Test Checklist

  • [ ] Create address with map search
  • [ ] Create address by typing (blur geocoding)
  • [ ] Create address manually (no geocoding)
  • [ ] Edit existing address
  • [ ] Set primary address
  • [ ] Delete address
  • [ ] Import addresses from CSV
  • [ ] Export addresses to CSV
  • [ ] Test with US address (USPS)
  • [ ] Test with international address (Google)
  • [ ] Test with invalid/incomplete address

Performance Check

bash
# Check country data loaded
php artisan tinker
>>> \Viewflex\FilamentAddress\Models\Country::count()
# Should be: 256

>>> \Viewflex\FilamentAddress\Models\CountrySubdivision::count()
# Should be: ~17,000+

Getting Help

Documentation:

Issues: Contact Support


Next Steps After Implementation

  1. Test thoroughly - Work through the Quick Test Checklist above
  2. Configure verification - Choose provider and test
  3. Set up import whitelist - Add your entity types
  4. Review authorization - Configure access control
  5. Customize forms - Add custom fields if needed
  6. Test internationally - Verify your target countries

Released under a commercial license.