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:
| Pattern | Use Case | Complexity | Time |
|---|---|---|---|
| Pattern 1: Basic (Polymorphic) | Standard address storage | Low | 15 min |
| Pattern 2: Embedded Fields | Single address per entity | Medium | 30 min |
| Pattern 3: Custom Extended Form | Add custom fields to address | Medium | 30 min |
| Pattern 4: Multiple Panels | Multi-tenant or different access levels | Medium | 45 min |
Pattern 1: Basic Polymorphic Addresses (Recommended)
Best for: Most use cases - multiple addresses per entity with full package features
Step 1: Install Package
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 — 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_secretStep 3: Add Trait to Models
// app/Models/Customer.php
use Viewflex\FilamentAddress\Concerns\HasAddresses;
class Customer extends Model
{
use HasAddresses;
}Step 4: Add Relation Manager to Resource
// 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:
// 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
// config/addresses.php
'import' => [
'allowed_entity_types' => [
'App\Models\User',
'App\Models\Customer', // Add your entities
'App\Models\Location',
],
],✅ Done! Test It
- Go to Customer resource → Edit → Addresses tab
- Click "New Address"
- Use map search or type address
- 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()
// 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
// 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
// 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
- Create/Edit Company
- Fill address fields or use map search
- 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
// 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
// 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
// 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
// app/Filament/Resources/Companies/Schemas/CompanyForm.php
use App\Filament\Schemas\CustomAddressForm;
Section::make('Company Address')
->schema(CustomAddressForm::schema())
->columns(2),✅ Done! Test It
- Create address with custom form
- Fill standard + custom fields
- 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
// 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 - MUST match your panel configuration
ADDRESS_PANEL_ID=customer
ADDRESS_PANEL_PATH=customerStep 5: Configure Authorization
// config/addresses.php
'authorization' => [
'enabled' => true,
// Optional: use a custom policy class
// 'policy' => \App\Policies\AddressPolicy::class,
],✅ Done! Test It
- Access both panels (/admin and /customer)
- Verify Address resources appear correctly
- Test authorization restrictions
Common Issues & Solutions
Issue: "Data truncated for column 'administrative_area_id'"
Cause: Used unsignedBigInteger instead of ulid()
Fix:
// 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:
// 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
# 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:
- README.md - Installation & quick start
- AUTHORIZATION.md - Permission configuration
- IMPORT-EXPORT.md - Bulk operations
- VERIFICATION.md - Address verification providers
Issues: Contact Support
Next Steps After Implementation
- Test thoroughly - Work through the Quick Test Checklist above
- Configure verification - Choose provider and test
- Set up import whitelist - Add your entity types
- Review authorization - Configure access control
- Customize forms - Add custom fields if needed
- Test internationally - Verify your target countries