Filament Address Pro - User Guide
Last updated: 2026-05-13
Complete guide to using Filament Address Pro in your Laravel application.
Table of Contents
- Getting Started
- Basic Usage
- Entering Addresses
- Advanced Usage
- International Addresses
- Address Verification
- Customization
- Best Practices
- Troubleshooting
Getting Started
Prerequisites
Before installing, ensure you have:
- Laravel 11, 12, or 13 application
- Filament 4.0 or 5.0 installed
- Google Maps API key with these APIs enabled:
- Geocoding API
- Maps JavaScript API
- Places API (for the address search field)
- Address Validation API (optional, for verification)
Installation
See INSTALLATION.md for full instructions, including beta access, Composer authentication, CI/CD environments, and scenario-based setup (fresh app vs. existing Filament app). Then follow the Quick Start in the README to run the install wizard.
Basic Usage
Adding Address Management to a Model
The most common use case is adding address management to existing models like User, Customer, or Company.
Step 1: Add the Trait
// app/Models/Customer.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Viewflex\FilamentAddress\Concerns\HasAddresses;
class Customer extends Model
{
use HasAddresses;
// ... rest of your model
}The HasAddresses trait provides:
addresses()- Relationship to all addressesprimaryAddress()- Get the primary addressverifiedAddresses()- Get only verified addresseshasPrimaryAddress()- Check if has a primary addresshasVerifiedAddresses()- Check if has any verified addressessetPrimaryAddress($address)- Set an address as primary
Step 2: Add the Relation Manager
// app/Filament/Resources/CustomerResource.php
namespace App\Filament\Resources;
use Viewflex\FilamentAddress\Filament\RelationManagers\AddressesRelationManager;
class CustomerResource extends Resource
{
// ... other methods
public static function getRelations(): array
{
return [
AddressesRelationManager::class,
];
}
}That's it! You now have a full address management interface on your Customer resource.
Entering Addresses
How the Form Works
Country selection is always the first step. Choosing a country determines which fields appear, how they are labeled, and which subdivision dropdowns are shown. Changing country resets the address fields and clears verification state.
Once a country is selected, the form shows fields appropriate to that country's address format — a US address has State and ZIP Code; a Japanese address has Prefecture, City, and Ward; some countries have no subdivision fields at all.
Subdivision dropdowns (state, city, district) are populated from the 17,000+ subdivisions seeded with the package — no API call required. For certain countries (FR, DE, ES, IT, NL), entering a postal code auto-populates the subdivision fields. See International Addresses for depth on how subdivisions work.
A live formatted preview below the fields updates in real time as you fill them, in both international and domestic formats.
Input Modes
How you enter the street address depends on the configured input mode (ADDRESS_INPUT_MODE):
| Mode | What the form shows | How address entry works |
|---|---|---|
autocomplete | Search box with map preview | Type to search; one selection fills all fields including coordinates. Marked verified automatically. |
geocoding | Map preview + Address Line 1 | Type in Address Line 1 and tab out — blur geocoding fills remaining fields and updates the map. Click Verify for provider check. |
manual | Address Line 1 only (no search box) | Fill every field by hand. Click Verify when done. |
auto | Best available based on API keys configured | Browser key present → autocomplete; server key only → geocoding; no keys → manual. |
In autocomplete mode, the Google Places search box and map preview appear at the top of the address section. The street address fields below are filled by the selection — you can edit them afterward if needed.
In geocoding mode, no search box appears. The map is shown (it updates when blur geocoding fills coordinates), but address entry is entirely through Address Line 1 — type or paste, tab out, and the remaining fields fill automatically. Then click Verify.
In manual mode, no API calls are made during entry. Verification is the only automated quality check.
Which mode should I use? See Verification Best Practices — For New Addresses for a comparison of the three flows and when each is appropriate.
Address Line Fields
address_line_1 and address_line_2 are the standard two street address lines — exactly what appears on a mailing label.
address_line_1— the primary street address: house number, street name, and optionally a secondary unit designator appended after a comma:135 S 18th St, Apt 4.address_line_2— an overflow line for secondary unit info, or the line a verification provider returned separately.
In geocoding and manual modes, when you type or paste into Address Line 1 and tab out, unit designators are automatically split into Address Line 2:
| What you type in Address Line 1 | Address Line 1 after geocoding | Address Line 2 |
|---|---|---|
135 S 18th St, Apt 4 | 135 S 18th St | Apt 4 |
100 Main St, Suite 200 | 100 Main St | Suite 200 |
135 S 18th St Unit 5B | 135 S 18th St | Unit 5B |
PO Box 1234 | PO Box 1234 | (empty) |
Recognised unit keywords: Apt, Suite/Ste, Unit, Room/Rm, #, Bldg/Building.
In autocomplete mode, the Places selection populates these fields directly. The split above does not apply — the provider's line structure is used as-is.
Additional Fields (bldg, box, ico)
These are hidden in a collapsed Additional Fields section at the bottom of the form. They exist for structured data that has dedicated semantic meaning beyond a mailing line.
| Field | Label | Purpose | Auto-prefix in formatted output |
|---|---|---|---|
bldg | Building | Building name or identifier ("Tower B", "Empire State Building") | None — value used as-is |
box | Box | PO Box number | PO Box (if not already present) |
ico | In Care Of | Addressee modifier (c/o recipient) | c/o (if not already present) |
For box and ico, enter just the identifying value — the label is added automatically. Type 1234 in Box and the formatted address shows PO Box 1234. For bldg, the value is used as-is — enter the full name or designator as it should appear (Tower B, Sony Building, Rockefeller Center).
Any filled additional fields appear as a separate line in the formatted address preview, inserted after address_line_2 (or after address_line_1 if there is no second line). If all three fields are empty, no extra line appears.
These fields are for deliberate, structured input. They are not populated automatically by geocoding or autocomplete. Both duplicate detection and quality scoring treat them as independent dimensions — a bldg difference between two otherwise identical addresses counts as a distinct address.
When to use which
| Scenario | Recommended field |
|---|---|
| Apartment / suite / unit | address_line_2 (populated automatically from line 1 in geocoding mode) |
| PO Box - typed directly into line 1 | Leave it in address_line_1 — geocoding handles it fine |
| PO Box - imported from a structured data source | box (cleaner formatting, structured storage) |
| Named building as part of the address | address_line_2 for casual entry; bldg when importing structured data |
| "In care of" recipient | ico |
Why doesn't geocoding move "PO Box 1234" to the
boxfield automatically?Verification providers (USPS, Smarty, Google) only look at
address_line_1andaddress_line_2. If we silently moved content to a dedicated field, the provider would receive an incomplete address and the standardization dialog would show a confusing mismatch. The dedicated fields are for cases where the caller controls each field explicitly — imports and API calls.
Advanced Usage
Using Address Form in Custom Forms
You can embed the address form in any Filament form:
use Viewflex\FilamentAddress\Filament\Schemas\AddressForm;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\TextInput;
public static function form(Form $form): Form
{
return $form->schema([
Section::make('Customer Information')
->schema([
TextInput::make('name')->required(),
TextInput::make('email')->email()->required(),
]),
Section::make('Primary Address')
->schema(AddressForm::schema())
->columns(2)
->collapsible(),
]);
}Using Address Form in Action Modals
You can embed the address form in any Filament Action modal, useful for "Add Address" buttons on pages, table rows, or anywhere you need the full form without navigating away.
Add the HandlesMapSelection trait and a $data property to your page or resource class, then spread AddressForm::schema() into the action's ->form():
use Viewflex\FilamentAddress\Concerns\HandlesMapSelection;
use Viewflex\FilamentAddress\Filament\Schemas\AddressForm;
use Filament\Actions\Action;
class CustomerProfile extends Page
{
use HandlesMapSelection;
// Required for Action forms with live updates (map selection, dynamic dropdowns)
public array $data = [];
protected function getActions(): array
{
return [
Action::make('addAddress')
->label('Add Address')
->icon('heroicon-o-plus')
->modalWidth('5xl')
->form([
...AddressForm::schema(),
])
->action(function (array $data) {
$this->record->addresses()->create($data);
}),
];
}
}Note:
HandlesMapSelectionautomatically detects whether it's inside a modal (mountedActions) or a page form, and routes map place selections to the correct form. No extra wiring is needed.
Accessing Address Data Programmatically
// Get all addresses
$customer = Customer::find(1);
$addresses = $customer->addresses;
// Get primary address
$primary = $customer->primaryAddress();
// Access address fields
if ($primary) {
$street = $primary->address_line_1;
$city = $primary->locality;
$state = $primary->administrative_area;
$zip = $primary->postal_code;
$country = $primary->country_code;
// Get coordinates
$latitude = $primary->lat;
$longitude = $primary->lon;
// Check verification status
if ($primary->is_verified) {
$provider = $primary->verification_provider; // 'usps', 'google', etc.
$verifiedAt = $primary->verified_at;
$metadata = $primary->verification_metadata; // JSON data
}
}Working with Multiple Addresses
// Create a new address
$customer->addresses()->create([
'country_code' => 'US',
'address_line_1' => '1600 Pennsylvania Avenue NW',
'locality' => 'Washington',
'administrative_area' => 'District of Columbia',
'postal_code' => '20500',
'is_primary' => true,
]);
// Update primary address
$newPrimary = $customer->addresses()->first();
$customer->setPrimaryAddress($newPrimary);
// Delete an address
$address = $customer->addresses()->find(2);
$address->delete();
// Filter addresses
$verified = $customer->verifiedAddresses();
$unverified = $customer->addresses()->unverified()->get();Using Geocoding Service Directly
For custom geocoding needs:
use Viewflex\FilamentAddress\Services\GeocodingService;
$geocoder = app(GeocodingService::class);
// Forward geocoding (address → coordinates)
$result = $geocoder->geocode(
'1600 Amphitheatre Parkway, Mountain View, CA',
'US'
);
if ($result['success']) {
$lat = $result['lat']; // 37.4220656
$lon = $result['lon']; // -122.0840897
$formatted = $result['formatted_address'];
$components = $result['components'];
// Address components
$street = $components['address_line_1'];
$city = $components['city'];
$state = $components['state'];
$stateAbbr = $components['state_abbr'];
$zip = $components['postal_code'];
// Verification data (if available)
if ($components['is_verified']) {
$provider = $components['verification_provider'];
$verifiedAt = $components['verified_at'];
}
} else {
$error = $result['error'];
}
// Reverse geocoding (coordinates → address)
$result = $geocoder->reverseGeocode(37.4220656, -122.0840897);
if ($result['success']) {
$address = $result['formatted_address'];
$components = $result['components'];
}Blur Geocoding Configuration (Power-User Options)
Control automatic geocoding behavior when users tab out of the address field. These options help reduce API costs while maintaining good UX.
Basic Configuration
# Enable/disable blur geocoding
ADDRESS_BLUR_GEOCODING_ENABLED=true
# Minimum characters before geocoding (prevents incomplete addresses)
ADDRESS_BLUR_MIN_LENGTH=10
# Skip if address already verified with coordinates
ADDRESS_BLUR_SKIP_IF_VERIFIED=true
# Debounce delay in milliseconds (0 = immediate)
ADDRESS_BLUR_DEBOUNCE_MS=0
# Full-address content detection: confidence boost when address_line_1 looks complete at blur time
ADDRESS_BLUR_DETECT_PASTE=truePower-User Options for API Cost Reduction
Option 1: Field Completeness Requirement
Determines which fields must be filled before blur geocoding triggers:
ADDRESS_BLUR_REQUIRE_COMPLETENESS=basicOptions:
any(default) - No requirements, geocode as soon as user tabs outbasic- Requires city OR postal code to be filled ⭐ Recommended for productioncomplete- Requires city AND state to be filled
Impact:
any→ 0% reduction (baseline)basic→ ~30% API call reductioncomplete→ ~50% reduction (but may frustrate users)
Example workflow with basic:
User types: "123 Main Street" → [tabs out]
System checks: Is city OR postal filled?
✅ Yes → Geocode and fill remaining fields
❌ No → Skip geocoding, wait for more dataOption 2: Smart Detection Level
Uses confidence scoring to prevent geocoding when data quality is too low:
ADDRESS_BLUR_SMART_DETECTION=smartOptions:
basic(default) - Always geocode (no filtering)smart- Only geocode if confidence >= 0.60 ⭐ Recommended for productionaggressive- Only geocode if confidence >= 0.80
Confidence scoring factors:
- Has street number: +0.25
- Token count >= 3: +0.15 (e.g., "123 Main Street" = 3 tokens)
- Has city: +0.20
- Has state: +0.20
- Has postal code: +0.20
- Full address detected (5+ tokens or comma): +0.10
- Cap: 1.00 maximum
Examples:
// Confidence: 0.25 (street) + 0.15 (3 tokens) + 0.20 (city) = 0.60 ✅
"123 Main Street" + city="Brooklyn"
// Result: Passes 'smart' threshold, geocodes
// Confidence: 0.25 (street) + 0.00 (2 tokens) + 0.20 (city) = 0.45 ❌
"123 Main" + city="Brooklyn"
// Result: Below 'smart' threshold, skips
// Confidence: 0.25 + 0.15 + 0.10 (paste) = 0.50 ❌
"123 Main St, Brooklyn, NY" (pasted, but no fields filled yet)
// Result: Below 'smart' threshold, skips
// Confidence: 0.25 + 0.15 + 0.20 + 0.20 = 0.80 ✅
"123 Main Street" + city="Brooklyn" + state="New York"
// Result: Passes 'aggressive' threshold, geocodesImpact:
basic→ 0% reduction (baseline)smart→ 40-55% API call reduction ⭐ Best balanceaggressive→ 60-80% reduction (may require too much data)
Recommended Production Configurations
Balanced (Best for Most Use Cases)
ADDRESS_BLUR_REQUIRE_COMPLETENESS=basic # City OR postal required
ADDRESS_BLUR_SMART_DETECTION=smart # 0.60 confidence thresholdResult: ~50-65% API call reduction with excellent UX
Cost-Optimized (High Volume / Budget-Conscious)
ADDRESS_BLUR_REQUIRE_COMPLETENESS=complete # City AND state required
ADDRESS_BLUR_SMART_DETECTION=aggressive # 0.80 confidence thresholdResult: ~70-85% API call reduction, but users must fill more fields
Development (No Restrictions)
ADDRESS_BLUR_REQUIRE_COMPLETENESS=any # No requirements
ADDRESS_BLUR_SMART_DETECTION=basic # No filteringResult: 0% reduction, fastest development experience
How the Guards Work Together
Blur geocoding uses a sequential guard system. Each guard can skip geocoding:
- Skip if being populated from reverse geocoding (prevents infinite loop)
- Skip if input is empty
- Skip if blur geocoding is disabled (
ADDRESS_BLUR_GEOCODING_ENABLED=false) - Skip if input too short (
ADDRESS_BLUR_MIN_LENGTH) - Skip if already verified (
ADDRESS_BLUR_SKIP_IF_VERIFIED=true) - Detect full-address entry (optional boost for confidence scoring)
- Check field completeness (
ADDRESS_BLUR_REQUIRE_COMPLETENESS) - Check confidence score (
ADDRESS_BLUR_SMART_DETECTION)
If any guard returns "skip", geocoding doesn't run (saves API call).
Monitoring & Optimization
Check your configuration:
# View current blur geocoding config
php artisan tinker
>>> config('addresses.blur_geocoding')Monitor API usage:
- View cache statistics: Admin Panel → Address API Cache
- Check audit logs: Admin Panel → Address API Audit Logs
- Track cost savings: Review cache hit ratio
Optimization tips:
- Start with
basic+smart(recommended defaults) - Monitor API costs for 1-2 weeks
- If costs too high → Try
complete+smartorbasic+aggressive - If users frustrated → Relax settings (
any+basicorbasic+smart) - Use cache statistics to measure impact of changes
International Addresses
The package supports 256 countries with intelligent address component extraction.
Why Structured Subdivision Data Matters
Most address systems store subdivisions as free text - users type "NY", "New York", "new york", or "N.Y." and each variant is stored as-is. Reports fragment, deduplication breaks, and you end up maintaining cleanup scripts forever.
Filament Address Pro takes a different approach. Every address links to a subdivision record by ULID, not by text string. The text field (administrative_area, locality, dependent_locality) is a display label; the ID (administrative_area_id, locality_id, dependent_locality_id) is the source of truth — a stable, canonical identifier that never drifts. Rename "Bombay" to "Mumbai" in the seed data and every address referencing that ID updates its display label automatically; the relationship was never stored as text. This has compounding benefits:
Unambiguous reporting -
GROUP BY administrative_area_idreturns exactly 50 US states - not 50 states plus "NY", "N.Y.", and "Calif." as separate buckets. BI queries work without fuzzy deduplication, and dashboards show clean geographic breakdowns from day one.Fuzzy matching corrects errors - Import a spreadsheet with "Masachusetts" or "TX" and the package resolves it to the correct subdivision record via fuzzy matching. The typo or abbreviation is absorbed on entry - it never reaches your database. Other systems store whatever the user typed, then fight the ambiguity in every downstream query.
Local names alongside English - When
ADDRESS_SHOW_LOCAL_NAMES=true, dropdown options display both the English name and the local-language name (e.g., "Tokyo / 東京都"). A Japanese admin searching for "東京都" and an English-speaking admin searching for "Tokyo" find the same record. This is practical for international teams - it eliminates the "which name do I search for?" problem.Hierarchical queries without external services - The
parent_idchain (district → city → state → country) lives in your database. "All addresses in Bavaria" is a single query through the hierarchy - no API call, no third-party geocoding, no string matching against "Bayern" vs "Bavaria" vs "BY".Postal code to subdivision resolution - For countries where the system inherently supports this (FR, DE, ES, IT, NL), entering a postal code auto-populates the subdivision. Type "75007" and the form fills in "Paris" (département). This is only possible because the reference data includes postal prefix mappings.
Zero external dependencies - The 17,000+ subdivisions ship as JSON inside the package. No runtime API calls to populate dropdowns, no data service subscription, no risk of a third party deprecating an endpoint. Your data update path is
composer update.
Country-Specific Formats
Different countries have different address formats. The package handles this automatically:
United States
1600 Pennsylvania Avenue NW
Washington, DC 20500United Kingdom
10 Downing Street
Westminster
London SW1A 2AAJapan (Hierarchical)
4-chōme-2-8 Shibakōen
Minato City
Tokyo 105-0011South Korea
123 Gangnam-daero
Gangnam-gu
Seoul 06236Working with Subdivisions
Countries have different subdivision levels (state/province → city → district):
use Viewflex\FilamentAddress\Models\Country;
use Viewflex\FilamentAddress\Services\CountryService;
// Get a country with hierarchical subdivisions
$china = Country::where('code', 'CN')->first();
// Check subdivision depth
$depth = $china->getActualSubdivisionDepth(); // 3 for China (province, city, district)
// Get subdivision options for dropdowns (hierarchical)
$provinces = CountryService::getAdministrativeAreaOptions('CN');
// Returns: ['province_id_1' => 'Beijing', 'province_id_2' => 'Shanghai', ...]
$cities = CountryService::getLocalityOptions('CN', $provinceId);
// Returns cities for the given province
// Example: ['city_id_1' => 'Dongcheng District', ...]
$districts = CountryService::getDependentLocalityOptions('CN', $cityId);
// Returns districts for the given city
// Example: ['district_id_1' => 'Wangfujing Subdistrict', ...]
// For countries with fewer levels (e.g., US has only states)
$us = Country::where('code', 'US')->first();
$depth = $us->getActualSubdivisionDepth(); // 1 for US (states only)
$states = CountryService::getAdministrativeAreaOptions('US');
// Returns: ['state_id_1' => 'Alabama', 'state_id_2' => 'Alaska', ...]Formatting International Addresses
use Viewflex\FilamentAddress\Services\CountryService;
// International format (with country)
$formatted = CountryService::formatAddress('JP', [
'address_line_1' => '4-chōme-2-8 Shibakōen',
'locality' => 'Minato City',
'administrative_area' => 'Tokyo',
'postal_code' => '105-0011',
]);
// Output:
// 4-chōme-2-8 Shibakōen
// Minato City, Tokyo 105-0011
// JAPAN
// Domestic format (no country, uses abbreviations)
$formatted = CountryService::formatAddressLocal('US', [
'address_line_1' => '1600 Pennsylvania Ave NW',
'locality' => 'Washington',
'administrative_area' => 'District of Columbia',
'postal_code' => '20500',
]);
// Output:
// 1600 Pennsylvania Ave NW
// Washington, DC 20500Postal Code Validation
use Viewflex\FilamentAddress\Models\Country;
use Viewflex\FilamentAddress\Services\CountryService;
// Get the postal code regex for a country, then validate
$pattern = Country::where('iso2', 'US')->first()->addressDefinition->postal_code_regex;
$isValid = CountryService::validatePostalCode($pattern, '20500'); // true
$isValid = CountryService::validatePostalCode($pattern, 'invalid'); // falseAddress Verification
Verification ensures deliverability and improves data quality. In the default interactive mode, the user clicks Verify to trigger the side-by-side comparison dialog; the provider runs at that point, not automatically. In silent mode, verification runs automatically on save with no dialog — suited for public-facing forms where surfacing a comparison dialog is undesirable.
Verification Providers
The package supports multiple verification providers:
USPS (Free for US)
- Best for: US addresses
- Cost: Free
- Accuracy: Authoritative US Postal Service data
- Setup: Register at developer.usps.com
USPS_CONSUMER_KEY=your_consumer_key
USPS_CONSUMER_SECRET=your_consumer_secretGoogle Address Validation
- Best for: International addresses in supported countries
- Cost: $0.005 per request
- Coverage: ~40 countries
- Setup: Enable in Google Cloud Console
Uses your existing Google Maps API key.
Smarty
- Best for: High-volume US addresses (US Street API) or 240+ countries (International Street API)
- Cost: ~$0.006 per lookup; plan-based pricing
- Free tier: Available (trial)
Configuration
// config/addresses.php
'verification' => [
'enabled' => true,
'provider' => 'auto', // auto, usps, google, smarty, none
// Auto-select provider by country
'country_providers' => [
'US' => 'usps', // Free USPS for US
// Others use Smarty or Google depending on provider_priority
],
'fallback_to_geocoding' => true, // Use geocoding if verification fails
'cache_ttl' => 86400, // Cache verification results for 24 hours
],How Verification Works
The flow differs by input mode:
Autocomplete mode — Selecting a place from the Google Places search box is treated as implicit verification. The form sets is_verified = true with verification_provider = google_places immediately. No Verify button press is needed.
Geocoding and Manual modes (interactive) — The user fills in fields (blur geocoding auto-fills components on tab-out in geocoding mode), then clicks Verify. The configured provider runs and, if successful, checks whether the standardized version differs meaningfully from what was entered. If it does, a side-by-side dialog lets the user accept the standardized address or keep their original. If there are no significant differences, the verification is applied silently.
Silent mode — Verification runs automatically on save, with no dialog. The provider result is applied or discarded silently.
Verification Status Badge
A badge below the address fields reflects the current state. The green ✓ Verified by {Provider} and blue ✓ Accepted by user badges are clickable — clicking opens a small popover showing the provider name and verification date. A Re-verify Address button inside the popover re-triggers verification, useful when an address needs to be re-checked after a data correction.
Provider outcome:
- If successful: Standardized components are applied,
is_verified = true, provider name and metadata stored - If fails: Falls back to geocoded data (if available),
is_verified = false, address still saved with best available data
Checking Verification Status
// Check if address is verified
if ($address->is_verified) {
echo "Verified by: " . $address->verification_provider;
echo "Verified at: " . $address->verified_at;
// Access verification metadata
$metadata = $address->verification_metadata;
// Example USPS metadata:
// ['delivery_point' => 'confirmed', 'dpv_match' => 'Y']
}
// Query verified addresses
use Viewflex\FilamentAddress\Models\Address;
$verified = Address::verified()->get();
$unverified = Address::unverified()->get();
$uspsVerified = Address::verifiedBy('usps')->get();Manual Verification
You can programmatically verify addresses:
use Viewflex\FilamentAddress\Services\Verification\AddressVerificationService;
$verifier = app(AddressVerificationService::class);
$result = $verifier->verify([
'address_line_1' => '1600 Pennsylvania Ave NW',
'locality' => 'Washington',
'administrative_area' => 'DC',
'postal_code' => '20500',
'country_code' => 'US',
]);
if ($result->isSuccessful()) {
$provider = $result->getProvider(); // 'usps'
$components = $result->getComponents();
// Access verified components
$address1 = $result->getAddressLine1();
$city = $result->getCity();
$state = $result->getState();
$zip = $result->getPostalCode();
$metadata = $result->getMetadata();
} else {
$error = $result->getError();
}Customization
Custom Address Form
Extend the base address form to add custom fields:
namespace App\Filament\Schemas;
use Viewflex\FilamentAddress\Filament\Schemas\AddressForm as BaseAddressForm;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\Select;
class CustomAddressForm extends BaseAddressForm
{
public static function schema(): array
{
return [
...parent::schema(),
Select::make('address_type')
->label('Address Type')
->options([
'residential' => 'Residential',
'commercial' => 'Commercial',
'po_box' => 'PO Box',
])
->default('residential')
->required(),
Textarea::make('delivery_notes')
->label('Delivery Instructions')
->maxLength(500)
->rows(3)
->hint('Special instructions for delivery'),
TextInput::make('gate_code')
->label('Gate/Access Code')
->maxLength(20),
];
}
}Then use your custom form:
use App\Filament\Schemas\CustomAddressForm;
public static function form(Form $form): Form
{
return $form->schema([
...CustomAddressForm::schema(),
]);
}Custom Verification Provider
Implement your own verification provider:
namespace App\Services;
use Viewflex\FilamentAddress\Services\Verification\VerificationProvider;
use Viewflex\FilamentAddress\Services\Verification\VerificationResult;
use Illuminate\Support\Facades\Http;
class CanadaPostVerifier implements VerificationProvider
{
public function verify(array $address): VerificationResult
{
// Make API request to Canada Post
$response = Http::post('https://addresscomplete.canadapost.ca/verify', [
'street' => $address['address_line_1'],
'city' => $address['locality'],
'province' => $address['administrative_area'],
'postal_code' => $address['postal_code'],
]);
if ($response->successful()) {
$data = $response->json();
return VerificationResult::success(
provider: 'canada-post',
components: [
'address_line_1' => $data['street'],
'locality' => $data['city'],
'administrative_area' => $data['province'],
'postal_code' => $data['postal_code'],
'country_code' => 'CA',
],
metadata: [
'delivery_mode' => $data['delivery_mode'],
'precision' => $data['precision'],
]
);
}
return VerificationResult::failure(
provider: 'canada-post',
error: 'Canada Post verification failed: ' . $response->body()
);
}
public function supportsCountry(string $countryCode): bool
{
return $countryCode === 'CA';
}
public function getName(): string
{
return 'canada-post';
}
public function isConfigured(): bool
{
return !empty(config('services.canada_post.api_key'));
}
}Register in a service provider:
use App\Services\CanadaPostVerifier;
use Viewflex\FilamentAddress\Services\Verification\AddressVerificationService;
public function boot(): void
{
$verificationService = app(AddressVerificationService::class);
$verificationService->registerProvider(app(CanadaPostVerifier::class));
}Configure in .env:
# Use Canada Post for CA addresses
ADDRESS_VERIFICATION_PROVIDER=auto
# Add to config/addresses.php
'verification' => [
'country_providers' => [
'CA' => 'canada-post',
],
],Best Practices
1. Always Enable Verification
Address verification improves data quality and reduces delivery issues:
ADDRESS_VERIFICATION_ENABLED=true
ADDRESS_VERIFICATION_PROVIDER=auto2. Use Primary Address Pattern
For models with multiple addresses, always designate one as primary:
// Good: Use the helper method
$customer->setPrimaryAddress($newAddress);
// Avoid: Direct updates (doesn't clear other primary flags)
$address->update(['is_primary' => true]);3. Cache Geocoding Results
The package caches verification results by default. For custom geocoding:
use Illuminate\Support\Facades\Cache;
$cacheKey = 'geocode:' . md5($address);
$result = Cache::remember($cacheKey, 86400, function () use ($address, $country) {
return app(GeocodingService::class)->geocode($address, $country);
});4. Handle Verification Gracefully
Never block the user if verification fails:
// Good: Save with geocoded data if verification fails
$result = $geocoder->geocode($userInput, $country);
if ($result['success']) {
$address->fill($result['components'])->save();
// is_verified will be set appropriately
}
// Avoid: Throwing errors on verification failure
if (!$result['components']['is_verified']) {
throw new Exception('Address verification failed'); // Bad!
}5. Use Subdivisions When Available
Store subdivision IDs for better data integrity:
// Good: Store subdivision ID
$address->administrative_area_id = $stateId;
$address->administrative_area = 'New York'; // Fallback text
// Benefit: Can query by subdivision relationships
$addressesInNY = Address::where('administrative_area_id', $stateId)->get();6. Validate Before Geocoding
Reduce unnecessary API calls:
// Validate required fields before geocoding
if (empty($data['address_line_1']) || empty($data['country_code'])) {
return; // Don't geocode incomplete addresses
}
// Check if address changed before re-geocoding
if ($address->address_line_1 === $oldAddress) {
return; // Don't re-geocode unchanged address
}7. Monitor API Usage
Track your Google Maps API usage to avoid surprise bills:
- Set up billing alerts in Google Cloud Console
- Use caching aggressively
- Consider limiting autocomplete suggestions
- Use USPS (free) for US addresses when possible
Troubleshooting
Geocoding Returns No Results
Symptoms: $result['success'] = false, error: "No geocoding results found"
Solutions:
- Check address format - Ensure address includes street, city, and country
- Verify API key - Check
GOOGLE_MAPS_SERVER_KEYis set and has the Geocoding API enabled - Enable Geocoding API - Confirm it's enabled in Google Cloud Console
- Check API restrictions - Ensure your domain/IP is allowed
- Test with known address - Try "1600 Pennsylvania Ave NW, Washington DC, US"
// Debug geocoding
$result = $geocoder->geocode('1600 Pennsylvania Ave NW', 'US');
if (!$result['success']) {
logger()->error('Geocoding failed', [
'error' => $result['error'],
'address' => '1600 Pennsylvania Ave NW',
'country' => 'US',
]);
}Verification Always Fails
Symptoms: is_verified = false for all addresses
Solutions:
- Check provider is configured - Verify USPS keys or Google API
- Confirm provider supports country - USPS only works for US
- Check verification is enabled -
ADDRESS_VERIFICATION_ENABLED=true - Review logs - Look for
logger()->info()messages about verification - Test provider directly - Use verification service standalone
// Test USPS provider
$provider = app(UspsProvider::class);
if (!$provider->isConfigured()) {
logger()->error('USPS not configured');
}
$result = $provider->verify([
'address_line_1' => '1600 Pennsylvania Ave NW',
'locality' => 'Washington',
'administrative_area' => 'DC',
'postal_code' => '20500',
'country_code' => 'US',
]);
logger()->info('USPS result', [
'success' => $result->isSuccessful(),
'error' => $result->getError(),
]);Subdivisions Not Loading
Symptoms: State/city dropdowns are empty
Solutions:
Seed country data:
bashphp artisan db:seed --class=Viewflex\\FilamentAddress\\Database\\Seeders\\CountriesDomainSeederVerify subdivisions exist:
phpuse Viewflex\FilamentAddress\Models\Country; $us = Country::where('code', 'US')->first(); $states = $us->subdivisions()->count(); logger()->info("US has {$states} states");Check subdivision depth:
php$depth = $us->getActualSubdivisionDepth(); // US should return 1 (states)
Autocomplete Not Appearing
Note: Google Places Autocomplete is available in the address search field (type in the search box after selecting a country). If it isn't appearing, verify your Google Maps API key is set and
ADDRESS_INPUT_MODEisautoorautocomplete, and that the Maps JavaScript API and Places API are enabled in Google Cloud Console.
Address Preview Not Updating
Symptoms: Live preview doesn't refresh when fields change
Solutions:
- Clear cache - Run
php artisan config:clear - Check browser console - Look for JavaScript errors
Next Steps
- Read the Overview - For installation and quick start
- Check Testing Guide - For test patterns and coverage
- Review Configuration - All available options
- Explore API Reference - Complete API documentation
- Review Examples - Common implementation patterns
Questions or issues? Contact support@viewflex.net
Built with ❤️ by Viewflex