fahiem/filament-pinpoint
Filament Pinpoint adds a location picker for Filament 4/5 with Google Maps or free Leaflet/OpenStreetMap. Includes search/autocomplete, click-to-set and draggable marker, current location, radius editing, reverse geocoding to fill address fields, dark mode, and translations.
## Getting Started
### Minimal Setup
1. **Install the package**:
```bash
composer require fahiem/filament-pinpoint
.env):
PINPOINT_PROVIDER=google # or 'leaflet'
GOOGLE_MAPS_API_KEY=your_api_key_here # Required for Google Maps
use Fahiem\FilamentPinpoint\Pinpoint;
Pinpoint::make('location')
->latField('lat')
->lngField('lng')
For a Location model with lat and lng fields:
public static function form(Form $form): Form
{
return $form->schema([
Pinpoint::make('location')
->label('Business Address')
->latField('lat')
->lngField('lng')
->searchable()
->draggable(),
TextInput::make('lat')->readOnly(),
TextInput::make('lng')->readOnly(),
]);
}
Key first steps:
.envlatField()/lngField() to your model fieldsPinpoint::make('location')
->latField('lat')
->lngField('lng')
->addressField('full_address')
->streetField('street')
->cityField('city')
->countryField('country')
->columnSpanFull();
Pattern: Chain address-related fields to auto-populate from geocoding.
Repeater::make('stores')
->schema([
Pinpoint::make('store_location')
->latField('latitude')
->lngField('longitude')
->addressField('address')
->height(300),
TextInput::make('store_name'),
])
->columns(2);
Pattern: Use Repeater for dynamic location collections (e.g., store chains).
public static function infolist(Infolist $infolist): Infolist
{
return $infolist->schema([
PinpointEntry::make('location')
->latField('lat')
->lngField('lng')
->columnSpanFull(),
]);
}
Pattern: Use PinpointEntry for read-only displays in resource views.
Pinpoint::make('search_area')
->latField('center_lat')
->lngField('center_lng')
->radiusField('radius_meters')
->defaultRadius(1000)
->height(500);
Pattern: Combine with a whereRaw query in your query scope:
->whereRaw("ST_DWithin(
ST_MakePoint(lng, lat),
ST_MakePoint({$centerLng}, {$centerLat}),
{$radius}
)")
Validation Rules: Add to your model:
protected static function rules(): array
{
return [
'lat' => 'required|numeric|between:-90,90',
'lng' => 'required|numeric|between:-180,180',
];
}
Database Storage:
Use decimal(10,8) for lat/lng fields to preserve precision.
Caching API Keys:
Store GOOGLE_MAPS_API_KEY in Laravel's .env or use config('services.google_maps.key').
Leaflet Optimization:
For high-traffic apps, preload Leaflet tiles or use a CDN for LEAFLET_TILE_URL.
Dark Mode: Leaflet's dark mode works out-of-the-box. For Google Maps, ensure your API key has dark mode support.
Google Maps API Quotas:
Reverse Geocoding Delays:
Leaflet Performance:
https://{s}.tile.openstreetmap.fr/.Repeater Field Paths:
data.items.0.location.lat) can break if not handled.->getRecord() in custom logic to access nested data.Dark Mode Inconsistencies:
API Key Errors:
Google Maps API error: RefererNotAllowed.Marker Not Showing:
latField/lngField values are valid (e.g., -6.200000, 106.816666).->defaultLocation() as fallback.Search Not Working:
NOMINATIM_URL is accessible (Nominatim may block bots).Radius Circle Missing:
radiusField is configured and the field exists in the model.->defaultRadius(500) for testing.Custom Geocoding Logic: Override reverse geocoding by extending the component:
use Fahiem\FilamentPinpoint\Pinpoint;
class CustomPinpoint extends Pinpoint
{
protected function getAddressFromCoordinates(): string
{
// Custom logic (e.g., call a local geocoding service)
return 'Custom Address';
}
}
Marker Icons: Dynamically set icons based on record data:
Pinpoint::make('location')
->latField('lat')
->lngField('lng')
->iconField('icon_type') // Field with 'restaurant', 'store', etc.
->iconMap([
'restaurant' => 'https://example.com/restaurant-marker.png',
'store' => 'https://example.com/store-marker.png',
]);
Event Listeners: Listen for marker changes:
Pinpoint::make('location')
->listen('marker-moved', function (array $data) {
// $data['lat'], $data['lng'], $data['address']
Log::info('Marker moved to:', $data);
});
Custom Providers: Add support for Mapbox or other providers by extending the base class and publishing a new config.
Localization: Extend translations for multi-language support:
// config/filament-pinpoint.php
'translations' => [
'nl' => [
'search_placeholder' => 'Zoek naar een locatie...',
],
];
Environment Overrides:
Per-field provider settings override global .env:
Pinpoint::make('location')->provider('leaflet'); // Overrides global setting
Default Values:
defaultLocation() uses Jakarta coordinates by default (-6.200000, 106.816666)..env:
GOOGLE_MAPS_DEFAULT_LAT=-33.8688
GOOGLE_MAPS_DEFAULT_LNG=151.2093
Height Units:
height() accepts pixels (e.g., 400) or percentages (e.g., 50%), but percentages may not work in all Filament layouts.
Radius Units:
Always use meters for radiusField() and defaultRadius().
Dark Mode:
Leaflet's dark mode requires LEAFLET_TILE_URL_DARK to be set in .env for full compatibility.
Pinpoint::make('location')
->height(0) // Hide initially
->listen('
How can I help you explore Laravel packages today?